{
  "schemaVersion": 1,
  "produto": "EscalaOper",
  "tipo": "caso técnico",
  "verificado_em": "2026-10-30",
  "autor": "Jeremias Cardoso",
  "relacao_comercial": "nenhuma; o sistema é do autor",
  "metodo": "Leitura do código e da documentação do repositório (somente leitura) e das notas do projeto guardadas na base de conhecimento do autor. O banco real não foi aberto e o sistema não foi executado. Os trechos são literais, exceto as substituições entre colchetes. Achados marcados no texto como lidos no código não foram testados.",
  "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 e CSS; <!-- --> em FXML).",
    "categorias": {
      "aplicacao_java": {
        "arquivos": 43,
        "linhas_fisicas": 8965,
        "linhas_nao_brancas": 7764,
        "linhas_de_codigo": 6750
      },
      "telas_fxml": {
        "arquivos": 12,
        "linhas_fisicas": 1367,
        "linhas_nao_brancas": 1204,
        "linhas_de_codigo": 1073
      },
      "estilos_css": {
        "arquivos": 2,
        "linhas_fisicas": 731,
        "linhas_nao_brancas": 655,
        "linhas_de_codigo": 564
      },
      "testes_java": {
        "arquivos": 2,
        "linhas_fisicas": 137,
        "linhas_nao_brancas": 116,
        "linhas_de_codigo": 109
      }
    },
    "testes_de_unidade": 12,
    "tabelas_criadas_pelo_sistema": [
      "usuarios",
      "escalas",
      "escala_periodos",
      "escala_lancamentos",
      "ferias_solicitacoes",
      "escala_lancamentos_bkp"
    ],
    "historico_git": {
      "commits": 52,
      "primeiro": "2026-06-05",
      "ultimo": "2026-06-14",
      "primeiro_commit": "54 files changed, 8149 insertions(+)"
    }
  },
  "substituicoes": [
    "[feriados locais]"
  ],
  "limites": [
    "Não é laudo independente de qualidade nem de segurança.",
    "O sistema não foi executado e o banco real não foi aberto; os achados sobre o ponto de restauração, o relançamento e a injeção de férias vêm da leitura do código.",
    "O repositório não registra quantas pessoas usam o sistema nem o tempo economizado; o texto não afirma ganho de produtividade.",
    "Não se publicam nomes de empresa, de pessoas, de cidade, caminhos locais nem credenciais."
  ],
  "fontes": [
    {
      "repositorio": "EscalaOper",
      "commit": "3830b6540f807bbacc08cfc94b6d5f4558de3c29",
      "data_do_commit": "2026-06-14"
    },
    {
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "commit": "e502569c734756c5dc339779a8187dd0093e90ad",
      "data_do_commit": "2026-10-22"
    }
  ],
  "alegacoes": [
    {
      "id": "sistema-desktop",
      "alegacao": "Sistema desktop para escalas operacionais: turnos, férias, ausências, sobreavisos, convocações, trocas e transporte.",
      "repositorio": "EscalaOper",
      "arquivo": "README.md",
      "linhas": [
        3,
        3
      ],
      "trecho": "Sistema **desktop** para gestão de **escalas operacionais**: turnos, férias, ausências, sobreavisos, convocações, trocas de horário, logística de transporte e um **dashboard consolidado de +40 dias** — com tema claro/escuro e notificações por e-mail."
    },
    {
      "id": "dados-reais",
      "alegacao": "O banco .accdb guarda dados reais e não é versionado.",
      "repositorio": "EscalaOper",
      "arquivo": "README.md",
      "linhas": [
        88,
        88
      ],
      "trecho": "> ⚠️ O `.accdb` (dados reais) **não** é versionado — veja o `.gitignore`."
    },
    {
      "id": "stack",
      "alegacao": "Java 21, JavaFX 21, Maven e Microsoft Access via UCanAccess.",
      "repositorio": "EscalaOper",
      "arquivo": "README.md",
      "linhas": [
        27,
        30
      ],
      "trecho": "| Linguagem | **Java 21** |\n| Interface | **JavaFX 21** (FXML + CSS) |\n| Build | **Maven** (fat jar via `maven-shade-plugin`) |\n| Banco | **Microsoft Access** (`.accdb`) via **UCanAccess** |"
    },
    {
      "id": "versao",
      "alegacao": "Versão 1.0.0.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Constantes.java",
      "linhas": [
        26,
        26
      ],
      "trecho": "    public static final String VERSAO = \"1.0.0\";"
    },
    {
      "id": "turnos",
      "alegacao": "Cinco valores de turno: Manhã, Tarde, Noite, Comercial e Folga.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Constantes.java",
      "linhas": [
        32,
        36
      ],
      "trecho": "TURNO_MANHA = \"Manhã\";\n    public static final String TURNO_TARDE = \"Tarde\";\n    public static final String TURNO_NOITE = \"Noite\";\n    public static final String TURNO_COMERCIAL = \"Comercial\";\n    public static final String TURNO_FOLGA = \"Folga\";"
    },
    {
      "id": "acento",
      "alegacao": "Antes das constantes, um erro de acento (\"Convocaçao\") quebrava filtros e cores sem dar erro.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Constantes.java",
      "linhas": [
        7,
        8
      ],
      "trecho": "Antes esses textos eram digitados à mão em vários arquivos — um erro de acento\n * (ex: \"Convocaçao\") quebrava filtros e cores sem dar erro nenhum."
    },
    {
      "id": "periodo-campos",
      "alegacao": "Cada período da escala tem quantidade de dias, turno, hora de entrada e hora de saída.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/model/EscalaPeriodo.java",
      "linhas": [
        7,
        10
      ],
      "trecho": "private int quantidadeDias;\n    private String turno;\n    private String horaEntrada;\n    private String horaSaida;"
    },
    {
      "id": "escala-lista",
      "alegacao": "A escala é uma lista de períodos.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/model/Escala.java",
      "linhas": [
        13,
        13
      ],
      "trecho": "    private List<EscalaPeriodo> periodos;"
    },
    {
      "id": "cadastro-turnos",
      "alegacao": "O cadastro de período oferece os cinco turnos.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/CadastroEscalaController.java",
      "linhas": [
        64,
        64
      ],
      "trecho": "        cbTurno.setItems(FXCollections.observableArrayList(Constantes.TURNO_MANHA, Constantes.TURNO_TARDE, Constantes.TURNO_NOITE, Constantes.TURNO_FOLGA, Constantes.TURNO_COMERCIAL));"
    },
    {
      "id": "cadastro-validacao",
      "alegacao": "O período exige pelo menos 1 dia e horário no formato HH:mm, ou em branco.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/CadastroEscalaController.java",
      "linhas": [
        230,
        242
      ],
      "trecho": "if (dias < 1) {\n                mostrarAlerta(\"Atenção\", \"A quantidade de dias deve ser pelo menos 1.\", Alert.AlertType.WARNING);\n                return;\n            }\n            String turno = cbTurno.getValue();\n            if (turno == null || turno.isEmpty()) {\n                mostrarAlerta(\"Atenção\", \"Selecione o Turno!\", Alert.AlertType.WARNING);\n                return;\n            }\n            String entrada = txtEntrada.getText().trim();\n            String saida = txtSaida.getText().trim();\n            if (!horaValida(entrada) || !horaValida(saida)) {\n                mostrarAlerta(\"Atenção\", \"Horário inválido. Use o formato HH:mm (ex.: 07:00) ou deixe em branco."
    },
    {
      "id": "lancamento-laco",
      "alegacao": "O lançamento caminha dia a dia da data de início à data de fim, grava um dia por vez e volta ao primeiro período quando a lista acaba.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/LancamentoEscalaController.java",
      "linhas": [
        178,
        190
      ],
      "trecho": "while (!dataAtual.isAfter(fim)) {\n                // NOVO: Passando o autorLogadoId como último parâmetro!\n                lancamentoBD.salvarDia(u.getId(), escala.getId(), dataAtual, periodoAtual.getTurno(), periodoAtual.getHoraEntrada(), periodoAtual.getHoraSaida(), autorLogadoId);\n                \n                dataAtual = dataAtual.plusDays(1);\n                diasRestantesNesteTurno--;\n\n                if (diasRestantesNesteTurno == 0) {\n                    indiceReceita++;\n                    if (indiceReceita >= receita.size()) indiceReceita = 0; \n                    periodoAtual = receita.get(indiceReceita);\n                    diasRestantesNesteTurno = periodoAtual.getQuantidadeDias();\n                }"
    },
    {
      "id": "lancamento-receita",
      "alegacao": "A lista de períodos é lida do banco como receita da escala; sem períodos, o lançamento é recusado.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/LancamentoEscalaController.java",
      "linhas": [
        145,
        147
      ],
      "trecho": "List<EscalaPeriodo> receita = escalaBD.listarPeriodosPorEscala(escala.getId());\n        if (receita == null || receita.isEmpty()) {\n            mostrarAlerta(\"Erro\", \"A escala selecionada não possui turnos configurados!"
    },
    {
      "id": "salvar-dia",
      "alegacao": "Cada dia vira uma linha na tabela de lançamentos.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        39,
        39
      ],
      "trecho": "        String sql = \"INSERT INTO escala_lancamentos (usuario_id, escala_id, data_escala, turno, hora_entrada, hora_saida, autor_id) VALUES (?, ?, ?, ?, ?, ?, ?)\";"
    },
    {
      "id": "unico-por-dia",
      "alegacao": "A tabela aceita uma linha por pessoa por dia.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/ConexaoBD.java",
      "linhas": [
        309,
        309
      ],
      "trecho": "                        + \"CONSTRAINT unq_user_data UNIQUE(usuario_id, data_escala)\""
    },
    {
      "id": "conflito",
      "alegacao": "Se já houver turnos no período, o sistema pergunta se deve sobrescrever.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/LancamentoEscalaController.java",
      "linhas": [
        166,
        166
      ],
      "trecho": "                alerta.setContentText(\"Este funcionário já possui turnos nestas datas. Deseja sobrescrever?\");"
    },
    {
      "id": "conflito-apaga",
      "alegacao": "Ao sobrescrever, todas as linhas da pessoa no período são apagadas, sem olhar o tipo do registro.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        30,
        30
      ],
      "trecho": "        String sql = \"DELETE FROM escala_lancamentos WHERE usuario_id = ? AND data_escala BETWEEN ? AND ?\";"
    },
    {
      "id": "tipos-registro",
      "alegacao": "Tipos de registro: Normal, Férias, Convocação, Ausência e Troca de Horário.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Constantes.java",
      "linhas": [
        39,
        43
      ],
      "trecho": "TIPO_NORMAL = \"Normal\";\n    public static final String TIPO_FERIAS = \"Férias\";\n    public static final String TIPO_CONVOCACAO = \"Convocação\";\n    public static final String TIPO_AUSENCIA = \"Ausência\";\n    public static final String TIPO_TROCA_HORARIO = \"Troca de Horário\";"
    },
    {
      "id": "convocacao-sobrescreve",
      "alegacao": "A convocação troca o turno e os horários da própria linha do dia.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        100,
        101
      ],
      "trecho": "String sql = \"UPDATE escala_lancamentos \" +\n                     \"SET turno = ?, hora_entrada = ?, hora_saida = ?, tipo_registro = 'Convocação'"
    },
    {
      "id": "folguistas",
      "alegacao": "Para convocar, o sistema lista quem está de folga na data.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/UsuarioBD.java",
      "linhas": [
        247,
        247
      ],
      "trecho": "                     \"WHERE el.data_escala = ? AND el.turno LIKE '%Folga%' \" +"
    },
    {
      "id": "ausencia-mantem-turno",
      "alegacao": "A ausência muda só o tipo e a observação; o turno do dia fica.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        300,
        300
      ],
      "trecho": "        String sql = \"UPDATE escala_lancamentos SET tipo_registro = 'Ausência', observacao = ?, autor_id = ? WHERE id = ?\";"
    },
    {
      "id": "ausencia-folga",
      "alegacao": "Ausência em dia de folga continua mostrando F; em dia de trabalho mostra o código do motivo.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/service/EscalaCodigoService.java",
      "linhas": [
        26,
        31
      ],
      "trecho": "// Ausência em dia de FOLGA: mantém o \"F\" (a sinalização fica na borda tracejada da célula).\n            if (turno != null && (turno.equalsIgnoreCase(Constantes.TURNO_FOLGA) || turno.equalsIgnoreCase(\"F\"))) {\n                return \"F\";\n            }\n            // Ausência em dia de trabalho: mostra o código do motivo (lido da observação).\n            if (obs == null) return \"OUT\";"
    },
    {
      "id": "motivos",
      "alegacao": "Motivos de ausência com código: LME, LMA, LPA, LGA, TRE, VAS e CHE.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/AjustesOperacionaisController.java",
      "linhas": [
        118,
        124
      ],
      "trecho": "\"Licença Médica / Atestado (LME)\",\n            \"Licença Maternidade (LMA)\",\n            \"Licença Paternidade (LPA)\",\n            \"Licença Gala / Casamento (LGA)\",\n            \"Treinamento (TRE)\",\n            \"Viagem a Serviço (VAS)\",\n            \"Compensando Horas Extras (CHE)\","
    },
    {
      "id": "troca-sobrescreve",
      "alegacao": "A troca de horário também sobrescreve o turno da linha.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        312,
        312
      ],
      "trecho": "        String sql = \"UPDATE escala_lancamentos SET turno = ?, observacao = ?, tipo_registro = 'Troca de Horário', autor_id = ? WHERE id = ?\";"
    },
    {
      "id": "troca-pares",
      "alegacao": "A troca entre duas pessoas roda numa transação: as duas linhas mudam juntas ou nenhuma muda.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        337,
        337
      ],
      "trecho": "            } catch (SQLException e) { conn.rollback(); LOG.log(Level.SEVERE, \"Erro ao realizar troca entre pares (transação)\", e); return false; }"
    },
    {
      "id": "sobreaviso-flag",
      "alegacao": "Sobreaviso é só uma marca: não altera turno nem tipo do dia.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        654,
        655
      ],
      "trecho": "// SOBREAVISO: marca/desmarca um lançamento SEM alterar o estado do dia.\n// É só uma flag — o turno, tipo, etc. continuam exatamente como estavam."
    },
    {
      "id": "sem-transporte",
      "alegacao": "A retirada do transporte é um marcador gravado no campo de observação.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        397,
        400
      ],
      "trecho": "// Usa a coluna 'observacao' para carimbar a exceção, mantendo o que já estava escrito!\n        // No Access, usamos '&' para juntar textos e 'IIF' para IFs inline.\n        String sql = \"UPDATE escala_lancamentos \" +\n                     \"SET observacao = IIF(observacao IS NULL, 'SEM_TRANSPORTE', observacao & ' | SEM_TRANSPORTE'"
    },
    {
      "id": "desfazer-pergunta",
      "alegacao": "Para desfazer ajustes em lote, o administrador informa qual é o turno correto.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/AjustesOperacionaisController.java",
      "linhas": [
        500,
        500
      ],
      "trecho": "        cbTurnoCorreto.setPromptText(\"Qual é o turno CORRETO agora?\");"
    },
    {
      "id": "desfazer-sql",
      "alegacao": "O desfazer em lote grava o mesmo turno em todos os dias do período.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        637,
        638
      ],
      "trecho": "SET turno = ?, tipo_registro = 'Normal', observacao = 'Ajuste Estornado (lote)', autor_id = ? \" +\n                 \"WHERE usuario_id = ? AND data_escala BETWEEN ? AND ?\""
    },
    {
      "id": "snapshot-substitui",
      "alegacao": "O ponto de restauração da escala é um só: criar outro substitui o anterior.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/controller/LancamentoEscalaController.java",
      "linhas": [
        251,
        251
      ],
      "trecho": "        confirmacao.setContentText(\"Isso vai salvar uma cópia exata de como a escala está agora. O backup anterior (se houver) será substituído. Deseja continuar?\");"
    },
    {
      "id": "snapshot-colunas",
      "alegacao": "O ponto de restauração copia oito colunas.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        344,
        345
      ],
      "trecho": "INSERT INTO escala_lancamentos_bkp (id, escala_id, usuario_id, data_escala, turno, tipo_registro, observacao, autor_id) \" +\n                           \"SELECT id, escala_id, usuario_id, data_escala, turno, tipo_registro, observacao, autor_id FROM escala_lancamentos\""
    },
    {
      "id": "lancamento-colunas",
      "alegacao": "A tabela de lançamentos tem também horários, justificativa e sobreaviso.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/ConexaoBD.java",
      "linhas": [
        295,
        308
      ],
      "trecho": "stmt.execute(\"CREATE TABLE escala_lancamentos (\"\n                        + \"id AUTOINCREMENT PRIMARY KEY, \"\n                        + \"usuario_id INTEGER NOT NULL REFERENCES usuarios(id) ON DELETE CASCADE, \"\n                        + \"escala_id INTEGER NOT NULL REFERENCES escalas(id), \"\n                        + \"data_escala DATETIME NOT NULL, \"\n                        + \"turno VARCHAR(50) NOT NULL, \"\n                        + \"hora_entrada VARCHAR(5), \"\n                        + \"hora_saida VARCHAR(5), \"\n                        + \"autor_id INTEGER, \" // <--- AQUI ESTÁ A MÁGICA! (Removido o REFERENCES)\n                        + \"tipo_registro VARCHAR(50) DEFAULT 'Normal', \"\n                        + \"justificativa VARCHAR(100), \"\n                        + \"observacao LONGVARCHAR, \"\n                        + \"sobreaviso YESNO, \" // flag de sobreaviso (mantém o estado do dia)\n                        + \"sobreaviso_info LONGVARCHAR, \""
    },
    {
      "id": "escala-id-obrigatorio",
      "alegacao": "Na tabela criada pelo sistema, todo lançamento exige o identificador da escala.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/ConexaoBD.java",
      "linhas": [
        298,
        298
      ],
      "trecho": "                        + \"escala_id INTEGER NOT NULL REFERENCES escalas(id), \""
    },
    {
      "id": "ferias-insert",
      "alegacao": "A injeção de férias em lote insere dias sem informar a escala.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        486,
        487
      ],
      "trecho": "String sqlInsert = \"INSERT INTO escala_lancamentos (usuario_id, data_escala, turno, tipo_registro, observacao) \" +\n                       \"VALUES (?, ?, 'Férias', 'Férias', 'Aprovada via Modo Admin')\";"
    },
    {
      "id": "ferias-transacao",
      "alegacao": "Antes, cada dia de férias era gravado sozinho e uma falha no meio deixava férias parciais; agora é tudo ou nada.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        490,
        492
      ],
      "trecho": "// TRANSAÇÃO: a injeção do período inteiro é \"tudo ou nada\".\n        // Antes cada dia era gravado em autocommit — se falhasse no meio, o\n        // colaborador ficava com férias PARCIAIS na escala. Agora reverte tudo."
    },
    {
      "id": "ferias-3-periodos",
      "alegacao": "Férias podem ser pedidas em até 3 períodos, com aprovação do RH.",
      "repositorio": "EscalaOper",
      "arquivo": "README.md",
      "linhas": [
        15,
        15
      ],
      "trecho": "- **Férias**: solicitação fracionada (até 3 períodos) com mapa visual, e análise pelo RH (aprovar/rejeitar) + modo admin para injetar/estornar em lote."
    },
    {
      "id": "feriados-moveis",
      "alegacao": "Feriados móveis calculados a partir da Páscoa.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Feriados.java",
      "linhas": [
        84,
        87
      ],
      "trecho": "LocalDate pascoa = calcularPascoa(ano);\n        f.add(pascoa.minusDays(2));   // Sexta-feira Santa\n        f.add(pascoa.minusDays(47));  // Carnaval (terça)\n        f.add(pascoa.plusDays(60));   // Corpus Christi"
    },
    {
      "id": "feriados-20-11",
      "alegacao": "O 20/11 entra como feriado nacional desde 2024.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/Feriados.java",
      "linhas": [
        17,
        17
      ],
      "trecho": "o 20/11 (Consciência Negra), que virou nacional em 2024."
    },
    {
      "id": "feriados-locais",
      "alegacao": "Feriados locais são configurados como dd/MM (todo ano) ou dd/MM/yyyy (um ano).",
      "repositorio": "EscalaOper",
      "arquivo": "config.properties.example",
      "linhas": [
        44,
        45
      ],
      "trecho": "# Formato: lista separada por virgula; cada item e dd/MM (todo ano) ou\n# dd/MM/yyyy (apenas naquele ano). Exemplo: [feriados locais]"
    },
    {
      "id": "transporte-regra",
      "alegacao": "Regra do transporte: Manhã e Comercial entram de manhã e saem à tarde; Tarde sai à noite; Noite sai de manhã do dia seguinte.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/service/TransporteService.java",
      "linhas": [
        30,
        32
      ],
      "trecho": "<li>Manhã/Comercial → entra de manhã, sai à tarde (mesmo dia).</li>\n     *   <li>Tarde → entra à tarde, sai à noite (mesmo dia).</li>\n     *   <li>Noite → entra à noite, sai de manhã (dia seguinte).</li>"
    },
    {
      "id": "cartaz-vagas",
      "alegacao": "A imagem do transporte mostra 6 nomes por grade e \"+N\" quando passa disso.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/CartazTransporte.java",
      "linhas": [
        71,
        71
      ],
      "trecho": "    /** Grade de nomes: 2 colunas x 3 linhas = 6 vagas visíveis (mais \"+N\" se passar). */"
    },
    {
      "id": "cartaz-lado",
      "alegacao": "A imagem é quadrada, com lado-base de 1.600 pixels.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/CartazTransporte.java",
      "linhas": [
        65,
        65
      ],
      "trecho": "    public static final double LADO = 1600;"
    },
    {
      "id": "outlook",
      "alegacao": "O e-mail é enviado pelo Outlook instalado, por um script temporário, sem abrir janela.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/OutlookMail.java",
      "linhas": [
        14,
        16
      ],
      "trecho": "Gera um script {@code .vbs} temporário (UTF-16LE com BOM) que cria um\n * {@code Outlook.Application}, monta um {@code MailItem} e o <b>envia silenciosamente</b>\n * ({@code OutMail.Send}) — sem abrir janela de composição."
    },
    {
      "id": "smtp-motivo",
      "alegacao": "O envio direto por servidor de e-mail não funcionava por senha de exemplo e bloqueio da rede.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-email-outlook.md.bak",
      "linhas": [
        10,
        10
      ],
      "trecho": "substituindo o SMTP/JavaMail que não enviava (senha de app placeholder + bloqueio de rede corporativa)"
    },
    {
      "id": "conexao-unica",
      "alegacao": "O UCanAccess recarrega o banco inteiro a cada conexão nova; por isso o sistema usa uma conexão só.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/ConexaoBD.java",
      "linhas": [
        26,
        29
      ],
      "trecho": "// CONEXÃO ÚNICA PERSISTENTE: o UCanAccess recarrega o banco inteiro a cada nova\n\t// conexão, então abrir/fechar uma por operação era lento. Como todo acesso ao\n\t// banco ocorre na thread da interface (sequencial), reaproveitar uma só conexão\n\t// é seguro e bem mais rápido."
    },
    {
      "id": "lock",
      "alegacao": "Com consultas em segundo plano, duas threads podiam usar a mesma conexão; um lock passou a deixar uma operação por vez.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/ConexaoBD.java",
      "linhas": [
        33,
        37
      ],
      "trecho": "// Lock que SERIALIZA o USO do banco. A conexão única do UCanAccess NÃO é thread-safe;\n\t// com as consultas em segundo plano (Task no Dashboard/Transporte/WhatsApp), duas threads\n\t// poderiam usar a mesma conexão ao mesmo tempo e corromper resultado/transação. O lock é\n\t// adquirido em conectar() e liberado no close() do proxy — como todo método usa\n\t// try-with-resources, só uma operação de banco roda por vez, SEM mudar os call-sites."
    },
    {
      "id": "backup-diario",
      "alegacao": "Backup do banco uma vez por dia, mantendo os últimos 15 dias.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/BackupService.java",
      "linhas": [
        28,
        28
      ],
      "trecho": "    private static final int DIAS_RETENCAO = 15;"
    },
    {
      "id": "pbkdf2",
      "alegacao": "Senhas guardadas com PBKDF2.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/util/PasswordUtil.java",
      "linhas": [
        29,
        29
      ],
      "trecho": "    private static final String ALGORITMO = \"PBKDF2WithHmacSHA256\";"
    },
    {
      "id": "shade",
      "alegacao": "Sem filtrar as assinaturas das dependências, o jar único falhava ao rodar.",
      "repositorio": "EscalaOper",
      "arquivo": "pom.xml",
      "linhas": [
        90,
        92
      ],
      "trecho": "<!-- Remove arquivos de assinatura das dependências (ex: BouncyCastle).\n                         Sem isso, o \"fat jar\" é visto como jar assinado inválido e falha ao rodar\n                         com \"Invalid signature file digest for Manifest main attributes\". -->"
    },
    {
      "id": "jar-standalone",
      "alegacao": "Em 05/06/2026 dois problemas quebravam o jar fora do ambiente de desenvolvimento.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-build-e-recursos.md.bak",
      "linhas": [
        12,
        12
      ],
      "trecho": "**Pegadinhas resolvidas em 2026-06-05 (estavam quebrando o jar standalone):**"
    },
    {
      "id": "fxml-nao-encontrado",
      "alegacao": "Um deles: as telas não eram achadas dentro do jar.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-build-e-recursos.md.bak",
      "linhas": [
        13,
        13
      ],
      "trecho": "Os **FXML/CSS agora ficam em `src/main/resources/{view,css}`** (layout Maven padrão) — foram movidos de `src/main/java` e o pom simplificado; o Maven os empacota sozinho. (Histórico: antes davam `Login.fxml não encontrado`.)"
    },
    {
      "id": "build-mascarado",
      "alegacao": "Uma edição em massa não casou com fim de linha CRLF, o build reaproveitou classes antigas e o Maven terminou com sucesso enquanto as telas não abriam.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-build-e-recursos.md.bak",
      "linhas": [
        18,
        18
      ],
      "trecho": "O **Eclipse** (auto-build) gerou `.class` com erro embutido (`java.lang.Error: Unresolved compilation problems`) em `target/classes`; o `mvn -o package` **pulou a recompilação** (classes mais novas que o fonte) e empacotou o jar quebrado → telas não abriam em runtime, mas `mvn` dava EXIT 0."
    },
    {
      "id": "accdb-apagado",
      "alegacao": "Numa limpeza de arquivos de teste, uma cópia do banco de 30/04 foi apagada por engano; o apagar do terminal não passa pela lixeira.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-bancos-locais.md.bak",
      "linhas": [
        17,
        17
      ],
      "trecho": "**Lição (2026-06-11):** ao \"limpar artefatos de smoke-test\" eu apaguei o `.accdb` da raiz do projeto achando que era lixo do teste — mas era de 30/abr (pré-existente). `rm` no bash do Windows é permanente (sem lixeira). Restaurei byte-idêntico da cópia em [pasta de cópias] (mesmo tamanho+timestamp)."
    },
    {
      "id": "servico-extraido",
      "alegacao": "A regra de siglas saiu do controlador da tela para um serviço testável.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/service/EscalaCodigoService.java",
      "linhas": [
        9,
        10
      ],
      "trecho": "Antes essa lógica vivia dentro do {@code VisualizacaoGeralController}, misturada com a\n * montagem da tabela."
    },
    {
      "id": "cidade-transporte",
      "alegacao": "O lançamento traz a cidade da pessoa, usada na logística do transporte.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/model/Lancamento.java",
      "linhas": [
        9,
        9
      ],
      "trecho": "    private String cidadeUsuario; // cidade do colaborador (logística de transporte)"
    },
    {
      "id": "email-gatilhos",
      "alegacao": "Sete pontos disparam e-mail: convocação, três de troca de turno, aprovação ou rejeição de férias e confirmação de pedido.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-email-outlook.md.bak",
      "linhas": [
        13,
        13
      ],
      "trecho": "os 7 pontos de disparo (convocação, troca de turno ×3, aprovar/rejeitar férias, confirmação de solicitação)"
    },
    {
      "id": "outlook-requisito",
      "alegacao": "O envio exige o Outlook instalado e configurado; o primeiro envio pode pedir confirmação.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-email-outlook.md.bak",
      "linhas": [
        14,
        14
      ],
      "trecho": "⚠️ Requer Outlook instalado/configurado; 1º envio programático pode pedir confirmação por política."
    },
    {
      "id": "estorno-atomico",
      "alegacao": "O estorno de férias também passou a ser atômico; antes, a escala podia ficar estornada com o pedido ainda aprovado.",
      "repositorio": "EscalaOper",
      "arquivo": "src/main/java/com/escalaoper/bd/LancamentoBD.java",
      "linhas": [
        544,
        545
      ],
      "trecho": "ser atômicos. Antes rodavam em autocommit — se o DELETE falhasse após o\n        // UPDATE, a escala ficava estornada mas a solicitação seguia \"Aprovado\"."
    },
    {
      "id": "backup-config",
      "alegacao": "O backup roda uma vez por dia, no primeiro acesso, ou sob demanda, e mantém os últimos 15 dias.",
      "repositorio": "EscalaOper",
      "arquivo": "config.properties.example",
      "linhas": [
        30,
        31
      ],
      "trecho": "# 1x/dia (no 1o acesso do dia) e ao clicar em \"Fazer backup agora\".\n# Mantem os ultimos 15 dias. Vazio = backup desativado."
    },
    {
      "id": "caminho-rede",
      "alegacao": "O arquivo do banco pode ficar num caminho de rede.",
      "repositorio": "EscalaOper",
      "arquivo": "config.properties.example",
      "linhas": [
        18,
        18
      ],
      "trecho": "# Caminho COMPLETO do arquivo do banco (pode ser um caminho de rede)."
    },
    {
      "id": "troca-senha",
      "alegacao": "Usuário novo é obrigado a trocar a senha no primeiro acesso.",
      "repositorio": "EscalaOper",
      "arquivo": "README.md",
      "linhas": [
        86,
        86
      ],
      "trecho": "- Novos usuários nascem com senha padrão e são **obrigados a trocá-la** no primeiro acesso; regras mínimas de complexidade na troca."
    },
    {
      "id": "licao-recompilar",
      "alegacao": "Depois de edição em massa: recompilar do zero e conferir que todas as telas carregam.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-build-e-recursos.md.bak",
      "linhas": [
        18,
        18
      ],
      "trecho": "**Lição:** depois de bulk-edit, sempre `rm -rf target/classes && mvn -o package` (recompila do zero) e valide o carregamento das telas com um probe `FXMLLoader.load(\"/view/X.fxml\")` (ver `probe/Diag.java`)"
    },
    {
      "id": "convencao-servico",
      "alegacao": "Convenção desde 11/06/2026: lógica de negócio nova vai para um serviço testável, não para a tela.",
      "repositorio": "base de conhecimento do autor (notas do projeto EscalaOper)",
      "arquivo": "Memorias-Versionadas/escalaoper/escalaoper-build-e-recursos.md.bak",
      "linhas": [
        22,
        22
      ],
      "trecho": "**Convenção daqui pra frente: lógica de negócio nova vai pra um service testável, não inline no controller.**"
    },
    {
      "id": "commit-inicial",
      "alegacao": "O primeiro commit é de 05/06/2026.",
      "repositorio": "EscalaOper",
      "commit": "3aa8ea54b808",
      "data": "2026-06-05",
      "trecho": "EscalaOper: logging, hash de senhas, correcoes e modernizacao de UI"
    },
    {
      "id": "commit-outlook-lock",
      "alegacao": "Em 10/06/2026: e-mail pelo Outlook e acesso ao banco serializado.",
      "repositorio": "EscalaOper",
      "commit": "febfa9d9c54a",
      "data": "2026-06-10",
      "trecho": "E-mail via Outlook (igual SIGO) + serializa acesso ao banco (bug critico)"
    },
    {
      "id": "commit-backup",
      "alegacao": "Em 10/06/2026: tela de configurações e backup do banco.",
      "repositorio": "EscalaOper",
      "commit": "2f534eade9a3",
      "data": "2026-06-10",
      "trecho": "Tela de Configuracoes (admin) + backup do banco + criar banco/admin se nao existir"
    },
    {
      "id": "commit-crlf",
      "alegacao": "Em 11/06/2026: correção da regressão do fim de linha CRLF.",
      "repositorio": "EscalaOper",
      "commit": "e65aa3e48cc2",
      "data": "2026-06-11",
      "trecho": "Fix regressao: import Constantes faltando em 3 controllers (perl nao casou em CRLF)"
    },
    {
      "id": "commit-testes",
      "alegacao": "Em 11/06/2026: camada de serviço e primeiros testes de unidade.",
      "repositorio": "EscalaOper",
      "commit": "cce82bf3f631",
      "data": "2026-06-11",
      "trecho": "Refator: camada de servico (EscalaCodigoService) + 1os testes unitarios"
    }
  ]
}
