{
  "schemaVersion": 1,
  "produto": "Regimen",
  "tipo": "Ficha funcional editorial com extratos sanitizados",
  "verificado_em": "2026-09-20",
  "autor": "Jeremias Cardoso",
  "relacao_comercial": "Produto do autor do Fluxonar; apresentação comercial.",
  "revisao_codigo": "a99efb0e9840fca83a5bac69f732786b78434669",
  "metodo": "Leitura do código, documentação funcional, guia de instalação e registros de entrega. Relatórios de QA são evidências históricas; nenhuma nova bateria do Regimen foi executada para este artigo. Nenhum banco operacional foi consultado.",
  "alegacoes": [
    {
      "id": "modulos",
      "alegacao": "Módulos de gestão e acesso por administrador, gerente e operador.",
      "arquivo": "src/main/java/br/com/cot/ui/PrincipalController.java",
      "linhas": [
        945,
        985
      ],
      "trecho": "        // --- Admin + Gerente (o Gerente tem acesso total, MENOS Perfis/Usuarios/Configuracoes) ---\n        modulos.put(\"Inicio\", new Modulo(null, this::carregarInicio)); // todos os perfis (conteudo varia por papel)\n        modulos.put(\"Relatório Mensal\", new Modulo(\n                Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarRelatorioMensal));\n        modulos.put(\"Colaboradores\", new Modulo(Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarColaboradores));\n        modulos.put(\"Mapa de Viagem\", adminGerente(\"/br/com/cot/view/mapa_equipe.fxml\"));\n        // Subestacoes: base do mapa logistico das viagens e das paradas (Admin+Gerente).\n        modulos.put(\"Subestações\", adminGerente(\"/br/com/cot/view/subestacoes.fxml\"));\n        modulos.put(\"Certificacoes\", new Modulo(Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarCertificacoes));\n        modulos.put(\"Processos\", adminGerente(\"/br/com/cot/view/processos.fxml\"));\n        modulos.put(\"Matriz RACI\", adminGerente(\"/br/com/cot/view/matriz_raci.fxml\"));\n        modulos.put(\"Objetivos\", adminGerente(\"/br/com/cot/view/objetivos.fxml\"));\n        modulos.put(\"Indicadores\", adminGerente(\"/br/com/cot/view/indicadores.fxml\"));\n        modulos.put(\"Viagens\", adminGerente(\"/br/com/cot/view/viagens.fxml\"));\n        // Pendencias: o quadro recebe o usuario logado (filtro \"Minhas Tarefas\").\n        modulos.put(\"Pendencias\", new Modulo(\n                Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarPendencias));\n        // Simulacao OTS: Admin e Gerente com os MESMOS poderes (agendar, registrar\n        // como foi e cadastrar cenarios).\n        modulos.put(\"Simulação OTS\", adminGerente(\"/br/com/cot/view/simulacoes.fxml\"));\n        // Tratativas Tecnicas: Admin e Gerente veem tudo, inclusive o tipo \"1:1\"\n        // (ali e registro tecnico, nao conversa de gestao de pessoas).\n        modulos.put(\"Tratativas Tecnicas\", adminGerente(\"/br/com/cot/view/feedbacks.fxml\"));\n        // Central de Feedback: gestao de pessoas do supervisor - SO o Admin.\n        // E ali (tabelas proprias) que mora a conversa confidencial.\n        modulos.put(\"Central de Feedback\", new Modulo(Set.of(Papel.ADMIN), this::carregarCentralFeedback));\n        modulos.put(\"Calendario\",\n                new Modulo(Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarCalendario));\n        modulos.put(\"Feriados\", adminGerente(\"/br/com/cot/view/feriados.fxml\"));\n        modulos.put(\"Financeiro\", new Modulo(\n                Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarFinanceiro));\n        modulos.put(\"Auditoria\", new Modulo(\n                Set.of(Papel.ADMIN, Papel.GERENTE), this::carregarAuditoria));\n\n        // --- Somente Administrador (dado sensivel / administracao do sistema) ---\n        modulos.put(\"Perfis (sensivel)\", new Modulo(Set.of(Papel.ADMIN), this::carregarPerfis));\n        // Usuarios: so o Admin, e a tela recebe o usuario logado (a auditoria\n        // precisa saber QUEM criou a conta ou redefiniu a senha de alguem).\n        modulos.put(\"Usuarios\", new Modulo(Set.of(Papel.ADMIN), this::carregarUsuarios));\n        modulos.put(\"Automações\", new Modulo(Set.of(Papel.ADMIN), this::carregarAutomacoes));\n        modulos.put(\"Configuracoes\", new Modulo(Set.of(Papel.ADMIN), this::carregarConfiguracoes));"
    },
    {
      "id": "pessoas",
      "alegacao": "Ficha 360 com restrição do perfil comportamental ao administrador.",
      "arquivo": "src/main/java/br/com/cot/ui/FichaColaboradorController.java",
      "linhas": [
        231,
        246
      ],
      "trecho": "\n    /**\n     * DADO SENSÍVEL (Regra de Ouro 7): o card de Desenvolvimento & Perfil DISC/PDI\n     * só mostra conteúdo ao ADMIN. Os demais veem o cadeado, sem seta e sem dados.\n     */\n    private void aplicarGateDisc() {\n        boolean admin = usuarioLogado != null && usuarioLogado.getPapel() == Papel.ADMIN;\n        if (admin) {\n            if (btnIrDisc != null) {\n                btnIrDisc.setVisible(true);\n                btnIrDisc.setManaged(true);\n            }\n            return;\n        }\n        if (btnIrDisc != null) {\n            btnIrDisc.setVisible(false);"
    },
    {
      "id": "certificacoes",
      "alegacao": "Alertas de certificações vencidas ou a vencer.",
      "arquivo": "src/main/java/br/com/cot/service/AlertasService.java",
      "linhas": [
        159,
        173
      ],
      "trecho": "        // --- Certificacoes (nominais): vencidas (critico) e vencendo (atencao) ---\n        List<Alerta> vencidas = new ArrayList<>();\n        List<Alerta> vencendo = new ArrayList<>();\n        for (Certificacao c : certificacaoDAO.listarTodas()) {\n            String s = SituacaoCertificacao.calcular(c, hoje);\n            String nome = ou(c.getColaboradorNome(), \"(sem nome)\");\n            String tipo = ou(c.getTipo(), \"Certificacao\");\n            Integer prazo = c.getValidade() == null ? null : (int) ChronoUnit.DAYS.between(hoje, c.getValidade());\n            String chave = \"CERT:\" + c.getId();\n            if (SituacaoCertificacao.VENCIDA.equals(s)) {\n                vencidas.add(new Alerta(nome + \" - \" + tipo + \" VENCIDA\", \"vermelho\", \"Certificacoes\", prazo, chave));\n            } else if (SituacaoCertificacao.BREVE.equals(s) || SituacaoCertificacao.TRATATIVAS.equals(s)) {\n                String txt = nome + \" - \" + tipo\n                        + (prazo == null ? \"\" : (prazo <= 0 ? \" (vence hoje)\" : \" (vence em \" + prazo + \" dia(s))\"));\n                vencendo.add(new Alerta(txt, \"laranja\", \"Certificacoes\", prazo, chave));"
    },
    {
      "id": "reunioes",
      "alegacao": "Conversão explícita de ação de reunião em pendência vinculada, sem duplicar a mesma ação.",
      "arquivo": "src/main/java/br/com/cot/service/ReuniaoAcaoService.java",
      "linhas": [
        28,
        62
      ],
      "trecho": "    public Pendencia converterEmPendencia(int acaoId) throws SQLException {\n        if (acaoId <= 0) {\n            return null;\n        }\n        try (Connection con = Database.abrirConexao()) {\n            con.setAutoCommit(false);\n            try {\n                ReuniaoAcao acao = acaoDAO.buscarPorId(con, acaoId);\n                if (acao == null) {\n                    con.commit();\n                    return null;\n                }\n                if (acao.isConvertida()) {\n                    Pendencia existente = pendenciaDAO.buscarPorId(con, acao.getPendenciaId());\n                    if (existente == null) {\n                        throw new SQLException(\"A acao possui um vinculo de Pendencia inconsistente: \"\n                                + acaoId);\n                    }\n                    con.commit();\n                    return existente;\n                }\n\n                Pendencia pendencia = montarPendencia(acao);\n                pendenciaDAO.inserir(con, pendencia);\n                LocalDateTime convertidaEm = LocalDateTime.now(RelogioNegocio.FUSO_HORARIO);\n                if (!acaoDAO.vincularPendenciaSeLivre(\n                        con, acao.getId(), pendencia.getId(), convertidaEm)) {\n                    throw new SQLException(\"A acao foi convertida por outra operacao. Recarregue a tela.\");\n                }\n                con.commit();\n                return pendencia;\n            } catch (SQLException | RuntimeException e) {\n                tentarRollbackSemMascarar(con, e);\n                throw e;\n            }"
    },
    {
      "id": "recorrencia",
      "alegacao": "Periodicidade mensal, trimestral e anual dos modelos de pendências.",
      "arquivo": "src/main/java/br/com/cot/service/PendenciaRecorrenciaService.java",
      "linhas": [
        229,
        247
      ],
      "trecho": "    public static LocalDate calcularProximoPrazo(LocalDate prazoAnterior, String periodicidade) {\n        Objects.requireNonNull(prazoAnterior, \"O prazo anterior e obrigatorio.\");\n        return switch (periodicidade) {\n            case \"Mensal\" -> prazoAnterior.plusMonths(1);\n            case \"Trimestral\" -> prazoAnterior.plusMonths(3);\n            case \"Anual\" -> prazoAnterior.plusMonths(12);\n            default -> throw new IllegalArgumentException(\n                    \"Periodicidade recorrente invalida: \" + periodicidade);\n        };\n    }\n\n    private Pendencia gerarProximaSeNecessario(Connection con, Pendencia origem)\n            throws SQLException {\n        if (!ehConcluida(origem) || origem.getModeloId() == null) {\n            return null;\n        }\n        if (origem.getProximaPendenciaId() != null) {\n            Pendencia existente = pendenciaDAO.buscarPorId(\n                    con, origem.getProximaPendenciaId());"
    },
    {
      "id": "metas",
      "alegacao": "Objetivos com responsável, progresso, situação e cronograma por trimestre.",
      "arquivo": "src/main/java/br/com/cot/ui/ObjetivosController.java",
      "linhas": [
        49,
        56
      ],
      "trecho": " * Controlador do Roadmap Anual Gantt de Objetivos/Metas (Opcao 3 aprovada pelo\n * Jeremias em 2026-09-19).\n *\n * <p>Tabela executiva: ID (OB-###), objetivo, categoria (chip), lider\n * responsavel, progresso (%), status (chip) e as QUATRO colunas de trimestre\n * com a barra do ciclo + losango de entrega + % no fim. Pills de categoria e\n * busca em cima; rodape com Total, Metas Concluidas, Em Andamento, Em Risco\n * (regra em {@link Objetivo#emRisco(LocalDate)}) e Orcamento Utilizado (dados"
    },
    {
      "id": "metas-pdf",
      "alegacao": "Exportação de metas e indicadores em PDF.",
      "arquivo": "src/main/java/br/com/cot/ui/ObjetivosController.java",
      "linhas": [
        701,
        708
      ],
      "trecho": "    /** Gera o relatorio PDF executivo de Metas e Indicadores e oferece para abrir. */\n    @FXML\n    private void aoGerarPdf() {\n        FileChooser fc = new FileChooser();\n        fc.setTitle(\"Salvar relatorio em PDF\");\n        fc.getExtensionFilters().add(new FileChooser.ExtensionFilter(\"Arquivo PDF\", \"*.pdf\"));\n        fc.setInitialFileName(\"metas_indicadores_\" + LocalDate.now() + \".pdf\");\n        File escolhido = fc.showSaveDialog(tabela.getScene().getWindow());"
    },
    {
      "id": "viagens",
      "alegacao": "Ordenação por tempo desde a última viagem realizada e destaque de pessoas sem viagem registrada.",
      "arquivo": "src/main/java/br/com/cot/ui/ViagensController.java",
      "linhas": [
        748,
        778
      ],
      "trecho": "    /** Ranking: quem está há mais tempo sem viajar aparece primeiro. */\n    private void montarEquidade() {\n        painelEquidade.getChildren().clear();\n        record Linha(Colaborador c, Long dias) {\n        }\n        List<Linha> linhas = new ArrayList<>();\n        for (Colaborador c : ativos) {\n            LocalDate ultima = null;\n            for (Viagem v : todas) {\n                if (v.getColaboradorId() == c.getId() && v.realizada() && v.getData() != null\n                        && (ultima == null || v.getData().isAfter(ultima))) {\n                    ultima = v.getData();\n                }\n            }\n            Long dias = ultima == null ? null : ChronoUnit.DAYS.between(ultima, LocalDate.now());\n            linhas.add(new Linha(c, dias));\n        }\n        linhas.sort((a, b) -> {\n            if (a.dias() == null && b.dias() == null) {\n                return a.c().getNome().compareToIgnoreCase(b.c().getNome());\n            }\n            if (a.dias() == null) {\n                return -1; // nunca viajou: topo da fila\n            }\n            if (b.dias() == null) {\n                return 1;\n            }\n            return Long.compare(b.dias(), a.dias());\n        });\n        int limite = Math.min(linhas.size(), 6);\n        for (int i = 0; i < limite; i++) {"
    },
    {
      "id": "financeiro",
      "alegacao": "Contratos e saldos, Opex, Capex, previsto e realizado.",
      "arquivo": "src/main/java/br/com/cot/ui/PainelFinanceiroController.java",
      "linhas": [
        39,
        50
      ],
      "trecho": " *\n * <p>Os numeros ficam SEPARADOS por natureza, sem misturar valores:\n * <ul>\n *   <li><b>Contratos</b> (todos, independente do ano): total contratado e saldo disponivel;</li>\n *   <li><b>Capex</b> do ano selecionado: orcado, realizado e disponivel (= orcado - realizado);</li>\n *   <li><b>Opex</b> do ano selecionado: orcado, realizado e disponivel;</li>\n *   <li><b>Visao geral</b>: previsto x realizado consolidado (Opex + Capex) do ano.</li>\n * </ul>\n *\n * <p>O <b>seletor de ano</b> no topo filtra Capex, Opex e a visao geral (que vem\n * do orcamento, que tem ano). Os contratos NAO sao filtrados por ano (eles tem\n * data de inicio/fim, nao \"ano de orcamento\")."
    },
    {
      "id": "relatorio",
      "alegacao": "Relatório mensal com distinção temporal e exclusão de narrativas sensíveis.",
      "arquivo": "src/main/java/br/com/cot/ui/RelatorioMensalController.java",
      "linhas": [
        180,
        187
      ],
      "trecho": "\n    @FXML\n    private void aoVerCriterios() {\n        Dialogos.info(\"Critérios do relatório\",\n                \"Movimento considera apenas a competência; acumulado anual vai de janeiro até o corte; \"\n                        + \"posições sem histórico são identificadas como retrato da emissão. \"\n                        + \"Dados pessoais sensíveis e narrativas não entram no PDF.\");\n    }"
    },
    {
      "id": "auditoria",
      "alegacao": "Registro de identidade, ação, módulo, resultado e campos alterados nas mutações cobertas.",
      "arquivo": "src/main/java/br/com/cot/audit/AuditedMutationService.java",
      "linhas": [
        64,
        75
      ],
      "trecho": "        try {\n            return transaction.execute(work, result -> AuditEvent.success(\n                    actor.getLogin(), actor.getPapel(), module, action, entity,\n                    entityIdResolver == null ? null : entityIdResolver.apply(result),\n                    changedFields, \"OPERACAO_CONCLUIDA\"));\n        } catch (SQLException | RuntimeException failure) {\n            recordFailureBestEffort(actor, module, action, entity, reasonCode(failure));\n            throw failure;\n        }\n    }\n\n    public <T> T execute("
    },
    {
      "id": "backup-regra",
      "alegacao": "Cópia diária do banco com retenção das 30 mais recentes.",
      "arquivo": "src/main/java/br/com/cot/db/Backup.java",
      "linhas": [
        20,
        31
      ],
      "trecho": " * Regras de seguranca (importante):\n *  - NUNCA mexemos no banco original; apenas o copiamos.\n *  - Fazemos no maximo 1 backup por dia (se ja existe o de hoje, nao repete).\n *  - Guardamos os ultimos {@value #MANTER} backups e removemos apenas os mais\n *    antigos (e somente arquivos de backup nossos, nunca dados do usuario).\n */\npublic final class Backup {\n\n    /** Subpasta (dentro de \"dados\") onde ficam as copias. */\n    private static final String PASTA_BACKUPS = \"backups\";\n\n    /** Quantos backups diarios manter. */"
    },
    {
      "id": "backup-execucao",
      "alegacao": "A cópia exige acesso à pasta; falha gera aviso e não impede abrir o sistema.",
      "arquivo": "src/main/java/br/com/cot/db/Backup.java",
      "linhas": [
        56,
        74
      ],
      "trecho": "\n            String hoje = LocalDate.now().format(DATA);\n            Path destino = pastaBackups.resolve(\"cot_\" + hoje + \".accdb\");\n\n            if (Files.exists(destino)) {\n                // Ja fizemos o backup de hoje.\n                return null;\n            }\n\n            Files.copy(banco, destino, StandardCopyOption.COPY_ATTRIBUTES);\n            limparAntigos(pastaBackups);\n            return destino;\n        } catch (Exception e) {\n            // Backup nunca pode derrubar o sistema; so registramos o aviso no log.\n            br.com.cot.util.Log.aviso(\"Nao foi possivel fazer o backup do banco: \" + e.getMessage());\n            return null;\n        }\n    }\n"
    }
  ],
  "documentacao_complementar": [
    {
      "arquivo": "docs/FUNCIONALIDADES.md",
      "sintese": "Cadastro e Ficha 360; processos/RACI e evidências; reuniões; passagem de turno; comunicados; base de conhecimento; reconhecimentos; financeiro; relatórios e rotinas de backup. O catálogo foi confrontado com o código e entregas posteriores."
    },
    {
      "arquivo": "docs/INSTALACAO.txt",
      "sintese": "Pacote Windows de 20/09/2026 com Java incluído. Banco em arquivo Access com caminho configurável. Mapa e busca externa usam internet; os controles locais dependem de acesso ao banco."
    },
    {
      "arquivo": "estado/estado.json",
      "sintese": "Tarefa 59 registra empacotamento e teste de abertura em cópia, concluídos em 20/09/2026. Tarefas 61 e 62 registram telas de metas, agenda e viagens com paradas por subestação ou cidade. Tarefa 11 permanece não iniciada: concorrência entre duas máquinas precisa de homologação."
    }
  ],
  "limites": [
    "Não é laudo independente de segurança, conformidade regulatória, desempenho ou disponibilidade.",
    "Benefícios descrevem a utilidade dos fluxos, sem economia de tempo, retorno financeiro ou produtividade medidos.",
    "Auditoria cobre os módulos instrumentados; backup exige pasta acessível e deve ter restauração conferida na implantação.",
    "Capturas de QA e bancos contêm informações internas e não são publicados nem enviados ao gerador de imagem.",
    "Não se publicam credenciais, nomes ou fotos de colaboradores, dados de clientes, valores operacionais, executáveis ou bancos.",
    "Integrações externas dependem de configuração e confirmação; não são anunciadas como envio automático irrestrito."
  ]
}
