{
  "schemaVersion": 1,
  "produto": "VE Facilites",
  "tipo": "caso técnico",
  "verificado_em": "2026-10-30",
  "autor": "Jeremias Cardoso",
  "relacao_comercial": "nenhuma; o sistema é do autor e é um piloto de demonstração, sem cliente contratado",
  "metodo": "Leitura do código, da documentação e do histórico do repositório no commit indicado (somente leitura), mais duas execuções locais em 30/10/2026 numa cópia do repositório: os testes da API que não exigem banco (mvn -B -ntp test, Java 21, Maven 3.9) e os testes do app (flutter test, Flutter 3.44). Os testes marcados para PostgreSQL não rodaram, e o sistema não foi usado num navegador nem num aparelho. Os trechos são literais, exceto as substituições entre colchetes e os cortes marcados com [...]. Nos marcos do histórico, \"assunto\" é a linha do commit e \"resumo\" é descrição do autor.",
  "contagem": {
    "regra": "Arquivos versionados (git ls-files) no commit indicado. Linha de código: linha não branca que não é só comentário (// e /* */ em Java, Dart e JavaScript, com o bloco reconhecido quando abre no início da linha; <!-- --> em HTML; -- em SQL). API: backend/src/main/java. Migrações: backend/src/main/resources/db/migration. Testes da API: backend/src/test/java. App: mobile/ve_facilites_app/lib e test. Páginas web: HTML e JavaScript das pastas de site, portal, painel, comercial e embed em docs/execucao, as páginas HTML de demo/ e o index.html da raiz. Ficam fora documentação, artefatos de execução, estado e configuração.",
    "categorias": {
      "api_java": {
        "arquivos": 84,
        "linhas_fisicas": 8490,
        "linhas_nao_brancas": 7439,
        "linhas_de_codigo": 6948
      },
      "migracoes_sql": {
        "arquivos": 15,
        "linhas_fisicas": 1213,
        "linhas_nao_brancas": 1066,
        "linhas_de_codigo": 799
      },
      "testes_api_java": {
        "arquivos": 27,
        "linhas_fisicas": 9451,
        "linhas_nao_brancas": 8371,
        "linhas_de_codigo": 7780
      },
      "app_dart": {
        "arquivos": 19,
        "linhas_fisicas": 4008,
        "linhas_nao_brancas": 3870,
        "linhas_de_codigo": 3855
      },
      "testes_app_dart": {
        "arquivos": 12,
        "linhas_fisicas": 2387,
        "linhas_nao_brancas": 2291,
        "linhas_de_codigo": 2265
      },
      "paginas_web": {
        "arquivos": 17,
        "linhas_fisicas": 24607,
        "linhas_nao_brancas": 22911,
        "linhas_de_codigo": 22406
      }
    },
    "testes": {
      "api_metodos_anotados_com_Test": 263,
      "api_metodos_em_classes_marcadas_postgres": {
        "classes": 8,
        "metodos": 91
      },
      "api_execucao_local_2026_10_30": "183 execuções em 17 classes, 0 falhas, 0 erros, 0 ignorados; BUILD SUCCESS (inclui LaudoPdfServiceTest com 30 e FluxoPontaAPontaTest com 25)",
      "app_chamadas_test_e_testWidgets": 66,
      "app_execucao_local_2026_10_30": "76 testes aprovados (All tests passed!), rodados a partir de uma cópia em caminho curto porque a ferramenta do Flutter falhou ao preparar o projeto no caminho longo"
    },
    "catalogo": {
      "disciplinas": 9,
      "criterios_de_checklist": 55,
      "detalhe": "A migração V4 cria 6 disciplinas e 45 critérios (elétrica 12, hidrossanitária 11, climatização 7, civil 6, segurança eletrônica e incêndio 5, avulso 4); a migração V11 acrescenta 3 disciplinas (fachada, manutenção predial e vistorias) e 10 critérios. Nenhuma migração desativa disciplina."
    },
    "entidades": 21,
    "paginas_web": {
      "chamadas_fetch_no_codigo": {
        "docs/execucao/p24/painel-operacao.html": 22,
        "docs/execucao/p20/portal-onda0.js": 3,
        "docs/execucao/site/site-institucional.html": 1,
        "docs/execucao/site/verificar-laudo.html": 1,
        "docs/execucao/p20/portal-cliente.html": 0
      },
      "telas_ilustrativas_do_portal": "seções b1 a b11 de portal-cliente.html"
    },
    "historico_git": {
      "commits": 296,
      "primeiro": "2026-08-20",
      "ultimo": "2026-10-30",
      "tags": 0,
      "primeiro_commit": "12 files changed, 2442 insertions(+)",
      "marcos": [
        {
          "commit": "a627da2",
          "data": "2026-09-13",
          "resumo": "checklists dinâmicos por disciplina"
        },
        {
          "commit": "4531ae5",
          "data": "2026-09-14",
          "resumo": "primeira bateria de testes contra o sistema rodando"
        },
        {
          "commit": "9aaa4e2",
          "data": "2026-09-15",
          "assunto": "feat(p37): remove os 11 pontos de fabricacao de fato juridico do laudo",
          "efeito": "o laudo deixa de completar responsável técnico, números de registro, ART e dados de empresa; a V7 anula as ARTs fabricadas"
        },
        {
          "commit": "7b2a9f3",
          "data": "2026-09-16",
          "resumo": "rota de início da OS e validação de foto obrigatória e de item crítico"
        },
        {
          "commit": "80b50a4",
          "data": "2026-09-17",
          "resumo": "hash estável no download do laudo e fim da degradação silenciosa"
        },
        {
          "commit": "32e6891",
          "data": "2026-10-29",
          "assunto": "fix(painel): corrigir syntax error no script do painel [...]"
        }
      ]
    }
  },
  "substituicoes": [
    "[empresa]",
    "[estado]",
    "[horário oficial]",
    "[endereço local de desenvolvimento]",
    "[...]"
  ],
  "limites": [
    "Não é laudo independente de qualidade, de segurança nem de conformidade com norma técnica.",
    "Os 91 testes marcados para PostgreSQL não rodaram; o sistema não foi usado num navegador nem num aparelho. Os achados sobre o app sem rede, o portal e os documentos vêm da leitura do código e da documentação.",
    "O repositório não registra cliente contratado, uso real nem ganho de produtividade; o texto não afirma nenhum dos três.",
    "Não se publicam nomes de empresa, de pessoas, de cidade, servidores, endereços, domínios, credenciais nem detalhes de segurança."
  ],
  "fontes": [
    {
      "repositorio": "VE Facilites",
      "commit": "60d9511f83e1b5ef1982a09622c7cb022a393f62",
      "data_do_commit": "2026-10-30"
    }
  ],
  "alegacoes": [
    {
      "id": "oportunidade",
      "alegacao": "A oportunidade é vender continuidade operacional de engenharia para condomínios e provar cada entrega.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/planejamento/01-sumario-posicionamento.md",
      "linhas": [
        82,
        82
      ],
      "trecho": "A oportunidade não é “digitalizar facilities” de forma ampla. É vender **continuidade operacional de engenharia** para condomínios e provar cada entrega com agenda, ordem de serviço, checklist, fotos, histórico, responsáveis e fechamento visível ao cliente."
    },
    {
      "id": "tese",
      "alegacao": "A tese é transformar execução técnica em evidência compreensível para síndicos e administradoras.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/planejamento/01-sumario-posicionamento.md",
      "linhas": [
        86,
        86
      ],
      "trecho": "> **Uma operação regional de engenharia integrada que coordena manutenção recorrente, responde a desvios conforme contrato e transforma execução técnica em evidência compreensível para síndicos e administradoras.**"
    },
    {
      "id": "evitar-compliance",
      "alegacao": "O planejamento manda evitar qualquer alegação de compliance não auditada.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/planejamento/01-sumario-posicionamento.md",
      "linhas": [
        173,
        173
      ],
      "trecho": "Evitar: “seu condomínio está ilegal”, “risco de multa” sem laudo e contexto, “zero falhas”, “cobertura total”, “SLA garantido” antes de capacidade comprovada e qualquer alegação de compliance não auditada."
    },
    {
      "id": "funil",
      "alegacao": "O propósito é operar o funil lead, ordem de serviço, evidência e relatório.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        13,
        13
      ],
      "trecho": "[...] **Propósito durável:** operar o funil lead → OS → evidência → relatório da [empresa] [...]"
    },
    {
      "id": "piloto-fechado",
      "alegacao": "Piloto fechado: cliente convidado, conta criada pela administração, sem autoatendimento, prospecção ou avulso.",
      "repositorio": "VE Facilites",
      "arquivo": "estado/estado.json",
      "linhas": [
        3227,
        3227
      ],
      "trecho": "[...] piloto fechado, com cliente convidado e conta criada pela administração, sem autoatendimento, prospecção ou avulso [...]"
    },
    {
      "id": "cliente-pendente",
      "alegacao": "Nomear o cliente-piloto e a data de início do piloto fechado continua pendente.",
      "repositorio": "VE Facilites",
      "arquivo": "estado/estado.json",
      "linhas": [
        4434,
        4434
      ],
      "trecho": "\"P51: nomear o cliente-piloto e a data de inicio do piloto fechado.\","
    },
    {
      "id": "rt-art-pendentes",
      "alegacao": "Sem responsável técnico e ART reais, o sistema não emite laudo nenhum.",
      "repositorio": "VE Facilites",
      "arquivo": "estado/estado.json",
      "linhas": [
        4432,
        4432
      ],
      "trecho": "[...] RT e numero de ART REAIS da [empresa]. Sem eles o sistema, corretamente, nao emite laudo nenhum. [...]"
    },
    {
      "id": "nao-atesta",
      "alegacao": "Java 21, Spring Boot 3.3.4 e PostgreSQL; o backend não atesta integração completa, operação offline, hospedagem nem uso real.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/README.md",
      "linhas": [
        3,
        3
      ],
      "trecho": "Java 21, Maven 3.9+, Spring Boot 3.3.4 e PostgreSQL. Este guia parte do pacote P14 e incorpora os remédios locais da Onda 0. O backend possui rotas de fotos e PDF; isso não atesta integração completa Flutter/web, operação offline, hospedagem ou uso real."
    },
    {
      "id": "spring-boot",
      "alegacao": "Spring Boot 3.3.4.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/pom.xml",
      "linhas": [
        10,
        10
      ],
      "trecho": "<version>3.3.4</version>"
    },
    {
      "id": "java-21",
      "alegacao": "Java 21.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/pom.xml",
      "linhas": [
        21,
        21
      ],
      "trecho": "<java.version>21</java.version>"
    },
    {
      "id": "openpdf",
      "alegacao": "O laudo usa OpenPDF 2.0.3.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/pom.xml",
      "linhas": [
        79,
        80
      ],
      "trecho": "<artifactId>openpdf</artifactId>\n            <version>2.0.3</version>"
    },
    {
      "id": "versao",
      "alegacao": "Versão 1.0.0-SNAPSHOT.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/pom.xml",
      "linhas": [
        16,
        16
      ],
      "trecho": "<version>1.0.0-SNAPSHOT</version>"
    },
    {
      "id": "readme-v13",
      "alegacao": "O README da API ainda descreve as migrações até a V13.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/README.md",
      "linhas": [
        140,
        140
      ],
      "trecho": "[...] Flyway aplica as migrações versionadas atuais (V1 a V13) [...]"
    },
    {
      "id": "perfis",
      "alegacao": "Quatro perfis: administração, gestão da operação, técnico de campo e síndico.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/domain/entity/Usuario.java",
      "linhas": [
        90,
        95
      ],
      "trecho": "public enum PapelUsuario {\n        ADMIN,\n        GESTOR_OPERACAO,\n        TECNICO_CAMPO,\n        SINDICO_CLIENTE\n    }"
    },
    {
      "id": "tipos-e-estados",
      "alegacao": "Tipos de ordem de serviço e os estados agendada, em andamento, concluída e cancelada.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/domain/entity/OrdemServico.java",
      "linhas": [
        250,
        263
      ],
      "trecho": "public enum TipoOS {\n        PREVENTIVA_MENSAL,\n        CORRETIVA_URGENTE,\n        VISTORIA_DIAGNOSTICA,\n        TERMOGRAFIA_PERIODICA,\n        AVULSO\n    }\n\n    public enum StatusOS {\n        AGENDADA,\n        EM_ANDAMENTO,\n        CONCLUIDA,\n        CANCELADA\n    }"
    },
    {
      "id": "itens-do-catalogo",
      "alegacao": "A ordem de serviço recebe os itens ativos do catálogo da sua disciplina.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        134,
        137
      ],
      "trecho": "if (os.getItensChecklist().isEmpty()) {\n            var itensCatalogo = catalogo.findByDisciplinaIdAndAtivoTrueOrderByItemOrdemAsc(os.getDisciplina().getId());\n            for (var cat : itensCatalogo) {\n                os.getItensChecklist().add(ChecklistItem.builder()"
    },
    {
      "id": "disciplinas-v4",
      "alegacao": "A migração V4 cria seis disciplinas, com incêndio dentro de segurança eletrônica.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/resources/db/migration/V4__checklists_dinamicos_disciplinas.sql",
      "linhas": [
        8,
        15
      ],
      "trecho": "INSERT INTO tb_disciplinas (sigla, nome, descricao, norma_referencia, ativo)\nVALUES\n  ('ELE', 'Engenharia Elétrica', 'Quadros de distribuição, subestações, barramentos, termografia e SPDA', 'NR-10, ABNT NBR 5410, NBR 5419, NBR 15572', TRUE),\n  ('HID', 'Sistemas Hidrossanitários', 'Barriletes, bombas de recalque, reservatórios de água, válvulas e drenagem pluvial', 'ABNT NBR 5626, NBR 8160, NBR 10844, NBR 14878', TRUE),\n  ('CIV', 'Engenharia Civil & Fachadas', 'Inspeção de trincas, fissuras, infiltrações, impermeabilização e percussão de revestimento', 'ABNT NBR 5674, NBR 16280, NBR 15575', TRUE),\n  ('CLI', 'Climatização & PMOC', 'Inspeção de filtros, serpentinas, drenos, bandejas de condensado e medição térmica', 'Lei Federal 13.589/2018, Portaria MS 3.523/1998, ABNT NBR 13971', TRUE),\n  ('SEG', 'Segurança Eletrônica & Incêndio', 'CFTV, gravação de imagens, controle de acesso, interfonia, portões e proteção contra incêndio', 'ABNT NBR 17240, ITs do Corpo de Bombeiros / [estado]', TRUE),\n  ('AVU', 'Serviço Avulso Especializado', 'Atendimento pontual residencial e comercial com evidência e aceite direto', 'Diretrizes Técnicas de Atendimento VE Facilites', TRUE)"
    },
    {
      "id": "cinco-no-planejamento",
      "alegacao": "O planejamento fala em cinco disciplinas.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/planejamento/01-sumario-posicionamento.md",
      "linhas": [
        54,
        54
      ],
      "trecho": "| Disciplinas: elétrica, climatização, hidráulica, civil e segurança eletrônica/automação. | Os planos comerciais podem coordenar as cinco disciplinas, desde que escopo, qualificação técnica e capacidade sejam comprovados por ativo e contrato. |"
    },
    {
      "id": "quatro-na-demo",
      "alegacao": "O roteiro de demonstração fala em quatro macro-disciplinas.",
      "repositorio": "VE Facilites",
      "arquivo": "LEIAME_DEMONSTRACAO.txt",
      "linhas": [
        46,
        46
      ],
      "trecho": "- Catálogo das 4 macro-disciplinas (Fachada, Predial, Hidráulica e Vistorias)."
    },
    {
      "id": "checklist-rascunho",
      "alegacao": "Os checklists são rascunho proposto, sem aplicação em ordem real antes do responsável técnico, com o bloco normativo vazio de propósito.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p10/checklists-v1.md",
      "linhas": [
        1,
        5
      ],
      "trecho": "# Checklists por disciplina — v1 (rascunho proposto)\n\n> ⚠️ **Não aplicar em OS real antes da validação do responsável técnico.** O **Bloco B** de cada\n> template está **vazio de propósito**: afirmar conformidade normativa sem que alguém qualificado\n> tenha conferido o texto da norma produz **prova fabricada** em nome da [empresa]. Ver"
    },
    {
      "id": "conclui-em-andamento",
      "alegacao": "Só ordem em andamento pode ser concluída.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        177,
        178
      ],
      "trecho": "if (os.getStatus() != OrdemServico.StatusOS.EM_ANDAMENTO)\n            throw ApiException.conflict(\"Somente OS EM_ANDAMENTO pode ser concluída.\");"
    },
    {
      "id": "valida-assinatura",
      "alegacao": "A assinatura é validada na conclusão.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        182,
        182
      ],
      "trecho": "signatures.validate(request.assinaturaBase64Png());"
    },
    {
      "id": "itens-exatos",
      "alegacao": "As respostas precisam cobrir exatamente os itens cadastrados na ordem.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        188,
        190
      ],
      "trecho": "var expected = os.getItensChecklist().stream().map(ChecklistItem::getId).collect(Collectors.toSet());\n        if (expected.isEmpty() || !expected.equals(answers.keySet()))\n            throw ApiException.invalid(\"Responda exatamente todos os itens cadastrados nesta OS.\");"
    },
    {
      "id": "foto-obrigatoria",
      "alegacao": "Item que exige foto precisa de ao menos uma foto.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        195,
        198
      ],
      "trecho": "if (item.getExigeFoto() != null && !item.getExigeFoto().isBlank()) {\n                if (fotos.countByChecklistItemId(item.getId()) == 0) {\n                    throw ApiException.invalid(\"O item '\" + item.getDescricaoCriterio() + \"' exige ao menos uma foto de evidência comprobatória antes da conclusão.\");\n                }"
    },
    {
      "id": "critico-observacao",
      "alegacao": "Item crítico exige observação técnica descrevendo o defeito.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        200,
        203
      ],
      "trecho": "if (answer.resultado() == ChecklistItem.ResultadoChecklist.CRITICO_IMEDIATO) {\n                if (answer.observacaoTecnica() == null || answer.observacaoTecnica().trim().length() < 5) {\n                    throw ApiException.invalid(\"O item '\" + item.getDescricaoCriterio() + \"' com resultado CRITICO_IMEDIATO exige observação técnica descritiva detalhando o defeito.\");\n                }"
    },
    {
      "id": "recibo",
      "alegacao": "A conclusão grava um recibo do envio junto com a ordem.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        219,
        221
      ],
      "trecho": "receipts.save(CompletionReceipt.builder().osId(id).authorId(user.id())\n                .payloadHash(hash).responseJson(encode(response)).build());\n        orders.saveAndFlush(os);"
    },
    {
      "id": "avulso-desligado",
      "alegacao": "Serviços avulsos não estão disponíveis no piloto fechado.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        235,
        236
      ],
      "trecho": "if (request.tipo() == OrdemServico.TipoOS.AVULSO) {\n            throw ApiException.conflict(\"Serviços avulsos não estão disponíveis no piloto fechado.\");"
    },
    {
      "id": "rollback-assinatura",
      "alegacao": "Um teste de integração cobre a falha na gravação da assinatura.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/test/java/br/com/vefacilites/platform/BackendIntegrationTest.java",
      "linhas": [
        208,
        208
      ],
      "trecho": "@Test void rollsBackChecklistStatusAndReceiptWhenSignatureInsertFails() throws Exception {"
    },
    {
      "id": "tipo-declarado",
      "alegacao": "O tipo da foto é declarado por quem fotografou; sem ele, nada é gravado.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/EvidenciaFotoService.java",
      "linhas": [
        49,
        50
      ],
      "trecho": "// Fronteira de confiança: o tipo é fato declarado por quem fotografou e vai para o laudo; sem ele, nada é\n        // gravado — nem o arquivo (a guarda vem antes de armazenar)."
    },
    {
      "id": "tipos-foto",
      "alegacao": "Tipos de foto: antes, durante e depois, entre outros.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/domain/entity/EvidenciaFoto.java",
      "linhas": [
        68,
        71
      ],
      "trecho": "public enum TipoFoto {\n        ANTES,\n        DURANTE,\n        DEPOIS,"
    },
    {
      "id": "tipos-foto-2",
      "alegacao": "Também defeito crítico e placa do equipamento.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/domain/entity/EvidenciaFoto.java",
      "linhas": [
        73,
        74
      ],
      "trecho": "DEFEITO_CRITICO,\n        PLACA_EQUIPAMENTO"
    },
    {
      "id": "sha-foto",
      "alegacao": "O servidor calcula o SHA-256 da foto na chegada.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/ArquivoStorageService.java",
      "linhas": [
        87,
        87
      ],
      "trecho": "MessageDigest md = MessageDigest.getInstance(\"SHA-256\");"
    },
    {
      "id": "relogio-unico",
      "alegacao": "Um único relógio da emissão: a data gravada e a data do PDF são o mesmo instante.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        42,
        42
      ],
      "trecho": "/** Único relógio da emissão: a data gravada na OS e a data de criação do PDF são o mesmo instante. */"
    },
    {
      "id": "nao-informado",
      "alegacao": "Ausência no PDF aparece como \"não informado\", nunca como valor plausível.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        45,
        49
      ],
      "trecho": "/**\n     * Marcador único de ausência no PDF: o leitor vê que o dado não foi informado, nunca um valor plausível.\n     * Privado de propósito: os testes provam pelo texto lido de volta do PDF, não pela constante (U9).\n     */\n    private static final String NAO_INFORMADO = \"não informado\";"
    },
    {
      "id": "so-concluida",
      "alegacao": "Só ordem concluída gera laudo.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        97,
        99
      ],
      "trecho": "// Apenas OS concluída gera laudo pericial formal\n        if (os.getStatus() != StatusOS.CONCLUIDA) {\n            throw ApiException.conflict(\"O Laudo Técnico Pericial só pode ser emitido para Ordens de Serviço concluídas.\");"
    },
    {
      "id": "original-preservado",
      "alegacao": "Depois da emissão, o sistema devolve o original preservado, confere o hash e não substitui um original inconsistente.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        102,
        115
      ],
      "trecho": "// O0-LAUDO: o original é o artefato preservado, nunca uma recomposição das fontes mutáveis.\n        byte[] original = os.getLaudoPdf();\n        if (original != null || os.getLaudoGeradoEm() != null || os.getLaudoHashSha256() != null) {\n            if (original == null) {\n                throw ApiException.conflict(\"O arquivo original deste laudo não está preservado no sistema. \"\n                        + \"A emissão registrada foi mantida; solicite o documento original à administração.\");\n            }\n            if (original.length == 0 || !os.temLaudoEmitido()\n                    || !calcularSha256(original).equals(os.getLaudoHashSha256())) {\n                throw ApiException.conflict(\"O arquivo preservado deste laudo está inconsistente. \"\n                        + \"O sistema não substituirá o original; procure a administração.\");\n            }\n            return original.clone();\n        }"
    },
    {
      "id": "grava-bytes",
      "alegacao": "Bytes, data e hash SHA-256 do PDF são gravados na ordem de serviço.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        161,
        167
      ],
      "trecho": "byte[] pdfBytes = baos.toByteArray();\n            String hashSha256 = calcularSha256(pdfBytes);\n\n            os.setLaudoPdf(pdfBytes.clone());\n            os.setLaudoGeradoEm(emitidoEm);\n            os.setLaudoHashSha256(hashSha256);\n            orders.saveAndFlush(os);"
    },
    {
      "id": "metadados-fixos",
      "alegacao": "Os metadados vêm do relógio da emissão; sem isso, dois PDFs do mesmo estado teriam hashes diferentes.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        177,
        183
      ],
      "trecho": "/**\n     * Metadados do arquivo vêm do mesmo relógio da emissão. Sem isto o OpenPDF grava a hora da máquina como\n     * CreationDate e um identificador de arquivo derivado de currentTimeMillis e memória livre: dois PDFs\n     * emitidos do mesmo estado teriam bytes (e hashes) diferentes sem que nada no laudo tivesse mudado.\n     * O identificador é derivado do código da OS e do instante de emissão — único por emissão, estável para\n     * o mesmo instante. O dicionário Info é o ponto de extensão do OpenPDF para o ID do trailer.\n     */"
    },
    {
      "id": "rt-art-nao-gera",
      "alegacao": "Nem o responsável técnico nem a ART podem ser escolhidos ou gerados pelo sistema.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        212,
        214
      ],
      "trecho": "/**\n     * Recusa a emissão quando falta responsabilidade técnica. Nem o RT nem o número da ART podem ser\n     * escolhidos ou gerados pelo sistema: são declaração de quem responde tecnicamente pela obra."
    },
    {
      "id": "recusa-sem-rt",
      "alegacao": "Sem responsável técnico ou ART, a emissão é recusada com mensagem que diz o que falta.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        229,
        230
      ],
      "trecho": "String fato = \"Esta ordem de serviço não tem \" + faltante\n                + \". O laudo técnico declara responsabilidade perante o CREA e não pode ser emitido sem esse dado. \";"
    },
    {
      "id": "imprime-ou-declara",
      "alegacao": "Toda célula imprime o que o banco tem ou declara a ausência.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        241,
        241
      ],
      "trecho": "/** Toda célula de texto do laudo passa por aqui: ou imprime o que o banco tem, ou declara a ausência. */"
    },
    {
      "id": "codigo-2d",
      "alegacao": "O PDF tem um código 2D de autenticidade.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        277,
        277
      ],
      "trecho": "// Célula QR Code / Datamatrix de Autenticidade Digital"
    },
    {
      "id": "n-a",
      "alegacao": "\"N/A\" leria como \"não se aplica\"; ausência é ausência.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        314,
        314
      ],
      "trecho": "// \"N/A\" leria como \"não se aplica\" — para uma OS concluída isso seria falso. Ausência é ausência."
    },
    {
      "id": "nao-avaliado",
      "alegacao": "Item sem resultado sai como \"NÃO AVALIADO\" e não conta como conforme.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        487,
        489
      ],
      "trecho": "// Item sem resultado registrado é exatamente isso: não avaliado. Não vira CONFORME nem entra\n            // na contagem de conformes — a obrigatoriedade de avaliar é regra da conclusão (P39), não do PDF.\n            String statusText = \"NÃO AVALIADO\";"
    },
    {
      "id": "foto-ilegivel",
      "alegacao": "Foto que não carrega impede a emissão.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        601,
        602
      ],
      "trecho": "throw ApiException.conflict(\"A evidência fotográfica '\" + foto.getId() + \"' (\" + foto.getTipo()\n                        + \") não pôde ser carregada do armazenamento. O laudo pericial não pode ser emitido com evidências corrompidas ou ausentes.\");"
    },
    {
      "id": "hash-por-foto",
      "alegacao": "Cada foto impressa traz o hash ou a frase de que ele não está disponível.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        624,
        627
      ],
      "trecho": "// Carimbo SHA-256 impresso — só o que o banco tem. Sem hash não há integridade a afirmar.\n            pInfo.add(new Chunk(\"Hash SHA-256: \", fBold));\n            boolean temHash = temTexto(foto.getHashSha256());\n            pInfo.add(new Chunk(temHash ? foto.getHashSha256() : \"hash não disponível — integridade não verificável\","
    },
    {
      "id": "assinatura-corrompida",
      "alegacao": "Assinatura corrompida impede a emissão.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        733,
        734
      ],
      "trecho": "throw ApiException.conflict(\"A assinatura digital registrada para a OS \" + os.getCodigoOs()\n                        + \" está corrompida ou em formato inválido. O laudo pericial não pode ser emitido com assinatura corrompida.\");"
    },
    {
      "id": "verificacao-hash-navegador",
      "alegacao": "A página de verificação calcula o SHA-256 do arquivo no navegador.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/site/verificar-laudo.html",
      "linhas": [
        548,
        551
      ],
      "trecho": "// Cálculo de Hash SHA-256 no Navegador via Web Crypto API\nasync function calcularSha256Arquivo(file) {\n  const arrayBuffer = await file.arrayBuffer();\n  const hashBuffer = await crypto.subtle.digest('SHA-256', arrayBuffer);"
    },
    {
      "id": "verificacao-autentico",
      "alegacao": "A consulta pública diz se a ordem está concluída, com responsável e ART, e se o PDF foi emitido com data e hash.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/README.md",
      "linhas": [
        92,
        92
      ],
      "trecho": "[...] **Autêntico** (`autentico: true`): a OS está concluída, tem responsável técnico e ART vinculados e o PDF foi emitido (data e hash registrados). [...]"
    },
    {
      "id": "assinatura-desenhada",
      "alegacao": "A assinatura é desenhada na tela com o pacote signature.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/lib/features/vistoria_checklist/presentation/signature_capture.dart",
      "linhas": [
        3,
        3
      ],
      "trecho": "import 'package:signature/signature.dart';"
    },
    {
      "id": "evidencia-de-captura",
      "alegacao": "A assinatura é evidência de captura, sem alegação de identidade criptográfica ou assinatura qualificada.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/README.md",
      "linhas": [
        80,
        80
      ],
      "trecho": "[...] É evidência de captura, sem alegação de identidade criptográfica ou assinatura qualificada."
    },
    {
      "id": "rascunho-sqlite",
      "alegacao": "O rascunho fica no SQLite do aparelho, por usuário e por ordem de serviço.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/lib/core/database/local_database.dart",
      "linhas": [
        44,
        52
      ],
      "trecho": "static Future<void> _createV2(Database db) => db.execute('''\n    CREATE TABLE inspection_drafts (\n      api_origin TEXT NOT NULL,\n      user_id TEXT NOT NULL,\n      order_id TEXT NOT NULL,\n      draft_json TEXT NOT NULL,\n      saved_at TEXT NOT NULL,\n      PRIMARY KEY (api_origin, user_id, order_id)\n    )"
    },
    {
      "id": "envio-exige",
      "alegacao": "O envio exige todos os itens respondidos, resumo, recebedor e assinatura; o corpo é gravado antes do POST e a retentativa repete o mesmo corpo.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/README.md",
      "linhas": [
        37,
        39
      ],
      "trecho": "- `Enviar conclusão` exige OS em andamento, todos os itens respondidos, resumo,\n  recebedor/cargo e assinatura. O corpo é gravado atomicamente antes do POST.\n  Em resposta perdida ou falha de recibo local, a retentativa repete o mesmo corpo."
    },
    {
      "id": "pendente-bloqueado",
      "alegacao": "Envio pendente fica bloqueado para edição.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/README.md",
      "linhas": [
        40,
        40
      ],
      "trecho": "[...] Envio pendente fica bloqueado para edição. [...]"
    },
    {
      "id": "pendente-codigo",
      "alegacao": "O código recusa alterar um envio pendente.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/lib/core/database/local_database.dart",
      "linhas": [
        180,
        183
      ],
      "trecho": "throw const AppFailure(\n            FailureKind.conflict,\n            'O conteúdo de um envio pendente não pode ser alterado.',\n          );"
    },
    {
      "id": "sem-rede",
      "alegacao": "Sem rede, só um rascunho salvo pode ser retomado; não há conclusão nem sincronização automática.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/README.md",
      "linhas": [
        45,
        46
      ],
      "trecho": "- Sem rede, um rascunho já salvo desta sessão pode ser retomado. Isso não conclui\n  a OS e não autentica offline. Não há início de OS nem sincronização automática."
    },
    {
      "id": "foto-na-hora",
      "alegacao": "A foto é enviada na hora; se falhar, o app mostra o erro.",
      "repositorio": "VE Facilites",
      "arquivo": "mobile/ve_facilites_app/lib/features/vistoria_checklist/presentation/checklist_execucao_screen.dart",
      "linhas": [
        301,
        305
      ],
      "trecho": "} catch (e) {\n      if (!mounted) return;\n      ScaffoldMessenger.of(context).showSnackBar(\n        SnackBar(\n          content: Text('Falha ao enviar foto: ${failureMessage(e)}'),"
    },
    {
      "id": "portal-demo",
      "alegacao": "O portal avisa quando está em demonstração com dados ilustrativos.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p20/portal-cliente.html",
      "linhas": [
        815,
        815
      ],
      "trecho": "<p id=\"portal-demo-notice\" data-only-demo role=\"status\">Demonstração local — dados ilustrativos; nenhuma operação real.</p>"
    },
    {
      "id": "portal-modo-real",
      "alegacao": "O portal tem um modo real, com as telas centrais ligadas à conta do usuário.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p20/portal-cliente.html",
      "linhas": [
        1036,
        1037
      ],
      "trecho": "<!-- O0 & O3: consulta e jornada real das 4 telas centrais para leigos restritas à identidade atual. -->\n    <section id=\"portal-real\" aria-labelledby=\"portal-real-h\" hidden data-only-real>"
    },
    {
      "id": "maquete-sem-api",
      "alegacao": "Os ramos ilustrativos do portal não fazem chamada à API.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p20/portal-onda0.js",
      "linhas": [
        26,
        26
      ],
      "trecho": "[...] Nenhum ramo ilustrativo [...] faz chamada à API."
    },
    {
      "id": "maquete-arquivo",
      "alegacao": "Telas da maquete avisam que não há envio de arquivo de verdade.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p20/portal-cliente.html",
      "linhas": [
        1562,
        1562
      ],
      "trecho": "<p class=\"hint\" id=\"b3-arq-h\">Nesta maquete o campo já vem preenchido — não há envio de arquivo de verdade.</p>"
    },
    {
      "id": "maquete-precos",
      "alegacao": "Os valores da maquete são ilustrativos e não são tabela de preços.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/p20/portal-cliente.html",
      "linhas": [
        2251,
        2251
      ],
      "trecho": "<p>Os valores desta maquete são ilustrativos e não representam tabela de preços — a política comercial ainda não foi definida.</p>"
    },
    {
      "id": "aparencia-nao-integra",
      "alegacao": "Uma revisão visual descrevia conexão real com o banco; as páginas não tinham chamada de rede.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        86,
        86
      ],
      "trecho": "[...] **2026-09-08 — Homologação visual não é integração.** [...] descrevia “conexão real com o PostgreSQL”; as três páginas web tinham zero `fetch`/`XMLHttpRequest` no código. [...]"
    },
    {
      "id": "pacote-demo",
      "alegacao": "Pacote autônomo de demonstração e testes.",
      "repositorio": "VE Facilites",
      "arquivo": "LEIAME_DEMONSTRACAO.txt",
      "linhas": [
        3,
        3
      ],
      "trecho": "PACOTE AUTÔNOMO DE DEMONSTRAÇÃO & TESTES"
    },
    {
      "id": "dois-cliques",
      "alegacao": "Dois cliques sobem o servidor local e abrem o navegador.",
      "repositorio": "VE Facilites",
      "arquivo": "LEIAME_DEMONSTRACAO.txt",
      "linhas": [
        22,
        23
      ],
      "trecho": "O script iniciará o servidor local portátil e abrirá automaticamente o seu navegador\npadrão (Chrome, Edge, etc.) no \"Hub Executivo de Demonstração\"."
    },
    {
      "id": "moldura-celular",
      "alegacao": "O simulador do app funciona numa moldura de celular no navegador.",
      "repositorio": "VE Facilites",
      "arquivo": "LEIAME_DEMONSTRACAO.txt",
      "linhas": [
        59,
        60
      ],
      "trecho": "6. SIMULADOR DO APP MOBILE (TÉCNICO DE CAMPO):\n   - Funciona dentro de uma moldura de celular no navegador (sem precisar de emulador!)."
    },
    {
      "id": "simulador-ilustrativo",
      "alegacao": "O simulador avisa que é ilustrativo e não autentica nem sincroniza dados.",
      "repositorio": "VE Facilites",
      "arquivo": "demo/app_simulator.html",
      "linhas": [
        730,
        730
      ],
      "trecho": "<p class=\"login-footer\">Simulador ilustrativo • não autentica nem sincroniza dados</p>"
    },
    {
      "id": "servidor-powershell",
      "alegacao": "O servidor da demonstração é um script PowerShell local.",
      "repositorio": "VE Facilites",
      "arquivo": "demo/server.ps1",
      "linhas": [
        1,
        1
      ],
      "trecho": "# Servidor local da demonstracao VE Facilites."
    },
    {
      "id": "atalho-python",
      "alegacao": "No Windows, o comando python pode ser só o atalho da loja; o inicializador passou a usar o servidor em PowerShell; empacotar o interpretador quebraria o envio pelo WhatsApp.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        57,
        57
      ],
      "trecho": "- **2026-09-21 — No Windows, o comando `python` existir não significa que o Python está instalado.** O atalho da Microsoft Store faz a busca do comando passar e abre uma janela que não serve página nenhuma. A cópia de trabalho do inicializador da demonstração passou a subir `demo/server.ps1` por soquete local, sem procurar Python. Em 2026-10-16 essa alteração do inicializador ainda estava fora do git. Empacotar o interpretador aumentaria o zip e quebraria o envio por WhatsApp."
    },
    {
      "id": "bateria-reprovou",
      "alegacao": "A bateria de 14/09 reprovou o piloto e aprovou só a fundação técnica.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/testes/qa-2026-09-14-2300.md",
      "linhas": [
        9,
        9
      ],
      "trecho": "- **Veredito: REPROVADO PARA O PILOTO (P08) — aprovado como fundação técnica.**"
    },
    {
      "id": "nenhuma-os-executa",
      "alegacao": "Nenhuma ordem saía de agendada: não havia rota para em andamento.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/testes/qa-2026-09-14-2300.md",
      "linhas": [
        17,
        17
      ],
      "trecho": "| **F-01** | **Nenhuma OS pode ser executada.** Não existe rota que leve a OS de `AGENDADA` a `EM_ANDAMENTO`; a conclusão exige `EM_ANDAMENTO`. Toda OS nasce agendada e morre agendada. | `OS-INICIO`, `OS-02a`, `PROP-03b`, `MOB-01` |"
    },
    {
      "id": "v7-art-fabricada",
      "alegacao": "A V7 corrige a ART fabricada e faz do não fabricar uma propriedade do banco.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/resources/db/migration/V7__corrige_art_fabricada_e_singleton_empresa.sql",
      "linhas": [
        1,
        5
      ],
      "trecho": "-- ============================================================================\n-- V7: CORRIGE A ART FABRICADA (PELA V5 E PELO CÓDIGO DE EMISSÃO), TORNA A\n--     IDENTIDADE DA EMPRESA SINGLETON E FAZ DO \"NÃO FABRICAR\" UMA PROPRIEDADE\n--     DO BANCO\n-- ============================================================================"
    },
    {
      "id": "art-fato-juridico",
      "alegacao": "ART é fato jurídico emitido pelo CREA; o sistema não pode deduzir RT nem ART.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/resources/db/migration/V7__corrige_art_fabricada_e_singleton_empresa.sql",
      "linhas": [
        20,
        21
      ],
      "trecho": "--   ART é fato jurídico emitido pelo CREA e o vínculo de RT é declaração de\n--   quem responde pela obra: o sistema não pode deduzir nenhum dos dois. A V5"
    },
    {
      "id": "pdf-idempotente",
      "alegacao": "Regerar o PDF com a hora atual mudava o hash; falha parcial deve abortar com 409.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        75,
        75
      ],
      "trecho": "- **2026-09-17 — Integridade pericial: download idempotente exige âncora de tempo determinística e tolerância zero a falhas parciais.** No OpenPDF e na geração de PDFs periciais, recriar metadados com `now()` a cada requisição altera `CreationDate`, `ModDate` e `FileID`, produzindo binários e hashes SHA-256 divergentes para o mesmo estado de documento e invalidando a via já entregue ao cliente. A idempotência estrita exige ancorar a geração no timestamp original da emissão (`os.getLaudoGeradoEm()`). Além disso, documentos periciais não toleram degradação silenciosa: substituir fotos corrompidas por placeholders ou assinaturas inválidas por linhas pontilhadas forja um laudo incompleto sob presunção de veracidade. Erros parciais devem abortar imediatamente com HTTP 409 Conflict explícito."
    },
    {
      "id": "tarefa-ato-humano",
      "alegacao": "Tarefa de ato humano não fecha por artefato; a de contratar o piloto foi fechada com um INSERT de teste.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        55,
        55
      ],
      "trecho": "[...] Tarefa cujo \"pronto quando\" é ato humano não fecha por artefato.** [...] P08 \"contratar piloto\" fechada com um INSERT de teste [...] Ao herdar estado de outra frente, separar as tarefas de ato humano e pedir a prova externa de cada uma (contrato, mensagem, painel). [...]"
    },
    {
      "id": "comercial-maquete",
      "alegacao": "O núcleo de OS e laudo existia; a camada comercial era maquete.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        41,
        41
      ],
      "trecho": "[...] **2026-10-23 — O fluxo comercial não existia; o que existia era o núcleo de OS/laudo, e a camada de captação, catálogo, planos, contratação, orçamento e comunicação era maquete.** [...]"
    },
    {
      "id": "sintaxe-silenciosa",
      "alegacao": "Um erro de sintaxe num script embutido fez a tela falhar sem alerta; a regra é validar a sintaxe de todos os scripts antes de publicar.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        39,
        39
      ],
      "trecho": "[...] Edições multilinhas em strings JS dentro de templates HTML (como interpolação de HTML na impressão de minuta) podem introduzir quebras de linha brutas ou duplicação de palavras-chave, resultando em SyntaxError durante o parse da tag script. [...] falhe silenciosamente sem disparar alertas visuais. [...] A validação sintática automatizada via node vm.Script em todos os scripts do frontend antes do deploy [...]"
    },
    {
      "id": "sem-pagamento",
      "alegacao": "A plataforma não é gateway de pagamento nem caixa.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        45,
        45
      ],
      "trecho": "[...] A plataforma de software não é gateway de pagamento nem ERP de caixa: orçamentos e faturamento são definidos com autonomia pelo prestador.** [...]"
    },
    {
      "id": "onda-4-depois",
      "alegacao": "Mensagens oficiais, pagamento e nota fiscal ficam para depois do piloto fechado.",
      "repositorio": "VE Facilites",
      "arquivo": "MEMORIA-PROJETO.md",
      "linhas": [
        705,
        705
      ],
      "trecho": "[...] A Onda 4 (WhatsApp Business API oficial, gateway de pagamentos e NFS-e de prefeitura) permanece delimitada para a fase posterior ao piloto fechado [...]"
    },
    {
      "id": "orcamento-antes",
      "alegacao": "A conclusão valida o orçamento antes de concluir.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        179,
        179
      ],
      "trecho": "validarOrcamentoAntesDeConcluir(os);"
    },
    {
      "id": "orcamento-exigido",
      "alegacao": "Ordem que exige orçamento prévio só conclui com orçamento registrado e aprovado pelo cliente.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        717,
        721
      ],
      "trecho": "private void validarOrcamentoAntesDeConcluir(OrdemServico os) {\n        boolean exigePrevio = Boolean.TRUE.equals(os.getExigeOrcamentoPrevio()) || os.getTipo() == OrdemServico.TipoOS.AVULSO;\n        if (orcamentosComerciais == null) {\n            if (exigePrevio) {\n                throw ApiException.conflict(\"Esta Ordem de Serviço exige o registro de orçamento pelo técnico no aplicativo e aprovação do cliente antes da conclusão.\");"
    },
    {
      "id": "nome-assinante",
      "alegacao": "O nome de quem assina é exigido.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/OrdemServicoService.java",
      "linhas": [
        180,
        181
      ],
      "trecho": "String signerName = request.nomeAssinante().trim();\n        if (signerName.length() < 2) throw ApiException.invalid(\"Nome do assinante exige ao menos dois caracteres após remover espaços externos.\");"
    },
    {
      "id": "cabecalho-conformidade",
      "alegacao": "O cabeçalho do PDF afirma conformidade normativa com a NBR 5674, a NBR 14037 e a legislação vigente.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        270,
        270
      ],
      "trecho": "Paragraph pNorms = new Paragraph(\"Conformidade Normativa: ABNT NBR 5674 (Manutenção Predial) · ABNT NBR 14037 (Operação e Uso) · Legislação Vigente\", fontNorms);"
    },
    {
      "id": "rodape-normas",
      "alegacao": "O rodapé do PDF repete as duas normas.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/service/LaudoPdfService.java",
      "linhas": [
        839,
        839
      ],
      "trecho": "String leftText = \"VE Facilites · Engenharia Predial — Laudo OS \" + codigoOs + \" — ABNT NBR 5674 / NBR 14037\";"
    },
    {
      "id": "verificacao-consulta",
      "alegacao": "A página calcula o hash do arquivo e consulta a API com ele.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/site/verificar-laudo.html",
      "linhas": [
        575,
        577
      ],
      "trecho": "const hash = await calcularSha256Arquivo(file);\n    document.getElementById('input-termo').value = hash;\n    await consultarLaudo(hash);"
    },
    {
      "id": "verificacao-endereco-local",
      "alegacao": "A página de verificação aponta para o endereço local de desenvolvimento da API, fixo no código.",
      "repositorio": "VE Facilites",
      "arquivo": "docs/execucao/site/verificar-laudo.html",
      "linhas": [
        490,
        490
      ],
      "trecho": "const API_BASE_URL = [endereço local de desenvolvimento];"
    },
    {
      "id": "nucleo-comercial",
      "alegacao": "Em 27/10, a migração V14 cria o núcleo comercial: clientes, endereços, serviços, planos, pedidos de visita e orçamento ligado à ordem.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/resources/db/migration/V14__nucleo_comercial_onda1.sql",
      "linhas": [
        4,
        12
      ],
      "trecho": "-- OBJETIVO: P51 / Onda 1 — Núcleo Comercial:\n--           1. Cliente comercial PF/PJ com estágios (tb_clientes_comerciais)\n--           2. Endereço de execução vinculado ao cliente (tb_enderecos_execucao)\n--           3. Serviço comercial com preço de visita em centavos sem default (tb_servicos_comerciais)\n--           4. Plano comercial versionado com valor mensal em centavos sem default (tb_planos_comerciais)\n--           5. Vínculo N:N entre plano comercial e serviços comerciais (tb_planos_comerciais_servicos)\n--           6. Assinatura comercial com snapshot imutável do plano (tb_assinaturas_comerciais)\n--           7. Solicitação de visita com geração de OS, atribuição e snapshot (tb_solicitacoes_visita)\n--           8. Orçamento vinculado à OS com decisão do cliente (tb_orcamentos_os)"
    },
    {
      "id": "comunicacao",
      "alegacao": "A migração V15 acrescenta exigência de orçamento prévio e registro de contatos.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/resources/db/migration/V15__campo_comunicacao_onda2.sql",
      "linhas": [
        4,
        7
      ],
      "trecho": "-- OBJETIVO: P51 / Onda 2 — Campo e Comunicação:\n--           1. Controle de opt-out / lista de supressão e opt-in comercial em tb_clientes_comerciais\n--           2. Colunas de snapshot adicionais e exigência de orçamento prévio em tb_ordens_servico\n--           3. Tabela persistida de log de contatos e comunicações (tb_logs_contato_comercial)"
    },
    {
      "id": "atalho-whatsapp",
      "alegacao": "O sistema monta atalhos para o WhatsApp.",
      "repositorio": "VE Facilites",
      "arquivo": "backend/src/main/java/br/com/vefacilites/platform/domain/entity/OrdemServico.java",
      "linhas": [
        247,
        247
      ],
      "trecho": "return \"https://wa.me/\" + e164 + \"?text=\" + URLEncoder.encode(texto, StandardCharsets.UTF_8);"
    }
  ]
}
