Metadata-Version: 2.4
Name: jupiter-subtes
Version: 1.1.5
Summary: Biblioteca para modernização do fluxo de trabalho do Tesouro do Estado do Rio de Janeiro
Author: EOP/SUPCONC
License: MIT
Project-URL: Homepage, https://github.com/bvkila/jupiter
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: selenium>=4.0.0
Requires-Dist: automaweb
Requires-Dist: requests
Requires-Dist: msal
Requires-Dist: pandas
Requires-Dist: Office365-REST-Python-Client>=2.6.2
Dynamic: license-file

# 🪐 jupiter-subtes

**jupiter-subtes** é uma biblioteca Python para automação dos fluxos de trabalho do Tesouro do Estado do Rio de Janeiro. Ela unifica, em um único pacote, três domínios de automação:

- **SIAFE-Rio2** — geração de documentos contábeis (GR, PDE, PDT, NP, NA, EV) por interface web e por API REST;
- **SEI** — controle de processos, análise de documentos, despachos, anexos e marcadores;
- **Microsoft 365** — SharePoint (arquivos e pastas) e Microsoft Graph (Outlook e Teams).

A biblioteca é **genérica**: não contém regras de negócio codificadas (contas, fontes, UGs). Toda a parametrização contábil é injetada em tempo de execução por dicionários, permitindo que o mesmo código atenda a diferentes setores sem alteração no fonte.

```python
from jupiter import Siafe, SEI, SharePoint, GraphAPI, configurar_log
```

---

## Índice

- [Instalação](#instalação)
- [Visão geral dos módulos](#visão-geral-dos-módulos)
- [Configuração de logging](#configuração-de-logging)
- [1. siafelibrary — SIAFE-Rio2](#1-siafelibrary--siafe-rio2)
- [2. seilibrary — SEI](#2-seilibrary--sei)
- [3. apipoint — SharePoint e Microsoft Graph](#3-apipoint--sharepoint-e-microsoft-graph)
- [Referência do `dict_map`](#referência-do-dict_map)
- [Arquivos de XPath](#arquivos-de-xpath)
- [Arquitetura e decisões de design](#arquitetura-e-decisões-de-design)

---

## Instalação

O módulo `apipoint` (SharePoint e Graph) depende de uma versão específica do `office365-rest-python-client` que ainda não foi publicada no PyPI. **Instale-a a partir do repositório oficial antes do `jupiter`:**

```bash
pip install git+https://github.com/vgrem/office365-rest-python-client.git@630e4e1f7977e28a8354174cc85896421425e4d7
pip install jupiter-subtes
```

Se você **não** utilizará o módulo `apipoint`, a instalação simples é suficiente:

```bash
pip install jupiter-subtes
```

### Dependências

| Biblioteca | Instalação | Uso |
|---|---|---|
| `selenium >= 4.0.0` | automática | Controle do navegador |
| `automaweb` | automática | Camada base de automação web (interna) |
| `pandas` | automática | Manipulação de DataFrames |
| `requests` | automática | Chamadas às APIs REST (SIAFE, SEI, Teams) |
| `msal` | automática | Suporte à autenticação Microsoft |
| `office365-rest-python-client` | **manual** (ver acima) | SharePoint e Graph |

> **Requisitos de ambiente:** o Edge deve estar instalado e atualizado. O `automaweb` gerencia o MSEdgeDriver automaticamente. A instalação do `office365-rest-python-client` via git exige que o `git` esteja disponível na máquina.

---

## Visão geral dos módulos

| Módulo | Classes / funções | Domínio |
|---|---|---|
| `siafelibrary` | `Siafe` | Automação web e API do SIAFE-Rio2 |
| `seilibrary` | `SEI` | Automação web do SEI |
| `apipoint` | `SharePoint`, `GraphAPI`, `configurar_log` | SharePoint, Outlook, Teams e logging |

As classes `Siafe` e `SEI` herdam de `automaweb.Navegador` e gerenciam o próprio navegador. `SharePoint` e `GraphAPI` operam exclusivamente por API.

---

## Configuração de logging

A biblioteca não registra nenhum handler por conta própria — todos os módulos emitem logs sob o logger `jupiter`. A função `configurar_log` centraliza a configuração e deve ser chamada **antes** de instanciar qualquer classe.

Ela cria até três destinos para os registros:

1. **Arquivo geral** — mensagens `INFO`, `WARNING` e `ERROR`;
2. **Arquivo de erros** — apenas `ERROR`;
3. **Interface gráfica** — opcional, via callback (para exibir mensagens ao usuário em tempo real).

```python
import logging
from jupiter import configurar_log, Siafe

# Configura os arquivos de log (chame no início do programa)
caminho_geral, caminho_erros = configurar_log(
    nome_programa="Rotina de Contabilização",
    pasta_geral="C:/Logs/geral",
    pasta_erros="C:/Logs/erros",
    callback_interface=None,   # ou uma função que recebe str, ex.: self.log
)

# Loggers do seu programa também são capturados se usarem o prefixo "jupiter."
log = logging.getLogger("jupiter.main")
log.info("Iniciando automação")

siafe = Siafe()   # a partir daqui, todos os logs do Siafe são gravados nos arquivos
```

Alternativamente, você pode configurar o logger `jupiter` manualmente com a API padrão do `logging`, caso precise de um formato ou destino específico.

---

## 1. siafelibrary — SIAFE-Rio2

A classe `Siafe` encapsula toda a interação com o SIAFE-Rio2, tanto pela interface web quanto pela API REST.

### 1.1. Autenticação

```python
from jupiter import Siafe

siafe = Siafe()

# Login pela interface web (necessário para geração de documentos via navegador)
sucesso = siafe.logar_siafe(versaoSiafe=1, usuario="12345678900", senha="senha")

# Login pela API REST (necessário para métodos *_API e consulta_flexvision)
siafe.logar_siafe_API(versaoSiafe=1, usuario="12345678900", senha="senha")
```

Ambientes disponíveis (`versaoSiafe`):

| Valor | Ambiente | Web | API |
|:---:|---|:---:|:---:|
| `1` | Produção | ✅ | ✅ |
| `2` | Beta / Testes | ✅ | ✅ |
| `3` | Homologação | ✅ | ✅ |
| `4` | SIAFE-Rio 1 | ✅ | — |

`logar_siafe` retorna `True`/`False` e trata internamente o pop-up de credenciais inválidas. `logar_siafe_API` armazena o token em `siafe.token` e o reutiliza nas chamadas subsequentes.

### 1.2. Geração de documentos em lote

O método `gerar_documento` é o ponto de entrada para contabilização em lote. Ele recebe:

- **`funcao`** — o método gerador do documento (`gerar_GR`, `gerar_PDE`, `gerar_PDT`, `gerar_NP`, `gerar_NA`, `gerar_EV` ou `gerar_PDT_API`);
- **`df`** — um DataFrame com os lançamentos;
- **`dict_map`** — dicionário de regras contábeis por tipo (ver [Referência do `dict_map`](#referência-do-dict_map));
- **`callback_sucesso`** — função opcional chamada a cada documento contabilizado.

| Método | Documento |
|---|---|
| `gerar_GR` | Guia de Recolhimento (orçamentária e extra-orçamentária) |
| `gerar_PDE` | Programação de Desembolso Extra-orçamentária |
| `gerar_PDT` | Programação de Desembolso de Transferência |
| `gerar_NP` | Nota Patrimonial |
| `gerar_NA` | Nota de Aplicação e Resgate |
| `gerar_EV` | Nota de Evento (múltiplos itens) |
| `gerar_PDT_API` | PDT enviada diretamente pela API REST |

O DataFrame deve conter, no mínimo, as colunas: `id`, `data`, `valor`, `observacao`, `tipo_id`, além de `num_documento` e `tempo_contab` inicializadas como `None` (o robô as preenche).

```python
import pandas as pd
from jupiter import Siafe

df = pd.DataFrame([
    {"id": 1, "data": "01/07/2025", "valor": 1500.00,
     "observacao": "Recolhimento referente a julho/2025",
     "tipo_id": "GR_FUNDO_A", "num_documento": None, "tempo_contab": None},
    {"id": 2, "data": "01/07/2025", "valor": 3200.50,
     "observacao": "Recolhimento referente a julho/2025",
     "tipo_id": "GR_FUNDO_B", "num_documento": None, "tempo_contab": None},
])

regras = {
    "GR_FUNDO_A": { ... },   # ver "Referência do dict_map"
    "GR_FUNDO_B": { ... },
}

def ao_contabilizar(id, num_documento, tempo_contab):
    print(f"[OK] id={id}  doc={num_documento}  tempo={tempo_contab}s")
    # persista o resultado no banco, planilha ou SharePoint aqui

siafe = Siafe()
if siafe.logar_siafe(versaoSiafe=1, usuario="12345678900", senha="senha"):
    siafe.gerar_documento(
        funcao=siafe.gerar_GR,
        df=df,
        dict_map=regras,
        callback_sucesso=ao_contabilizar,
    )
```

O `callback_sucesso` é chamado a cada documento gerado, permitindo **persistência incremental** — se a rotina for interrompida, os documentos já contabilizados estão salvos e o DataFrame reprocessa apenas as linhas com `num_documento` ainda vazio.

### 1.3. Nota de Evento (EV) com múltiplos itens

Diferente dos demais, a `gerar_EV` recebe os itens em um DataFrame aninhado no próprio dicionário, na chave `itens` (colunas `id`, `evento`, `credor`, `valor`):

```python
import pandas as pd

regras_ev = {
    "EV_FOLHA": {
        "UG":  "123456",
        "IEF": "1",
        "itens": pd.DataFrame([
            {"id": 1, "evento": "540101", "credor": "12345678000199", "valor": 1000.00},
            {"id": 2, "evento": "540102", "credor": "98765432000155", "valor":  750.50},
        ]),
    },
}

siafe.gerar_documento(funcao=siafe.gerar_EV, df=df, dict_map=regras_ev)
```

Para duplicar uma Nota de Evento já existente alterando apenas a data e a observação:

```python
siafe.copiar_EV(num_doc="2025EV000123", nova_data="01/08/2025", nova_obs="Reemissão agosto/2025")
```

### 1.4. Geração de PDT pela API REST

Para PDTs, além do fluxo web, a biblioteca oferece um caminho por API — mais rápido e sem navegador. O fluxo tem duas etapas: obter a estrutura de uma PDT-modelo com `dicionario_PDT` e enviá-la com `gerar_PDT_API`.

```python
from jupiter import Siafe

siafe = Siafe()
siafe.logar_siafe_API(versaoSiafe=1, usuario="12345678900", senha="senha")

# 1. Monta o dicionário-base a partir de uma PDT existente (usada como modelo)
modelo = siafe.dicionario_PDT(exercicio=2025, codigoUG=123456, num_documento="2025PD000045")

# 2a. Envio individual
num_doc, tempo = siafe.gerar_PDT_API(
    exercicio=2025,
    dicionario=modelo,
    data="01/07/2025",
    valor=1500.00,
    observacao="Transferência referente a julho/2025",
)

# 2b. Ou em lote, reutilizando gerar_documento
siafe.gerar_documento(
    funcao=siafe.gerar_PDT_API,
    df=df,
    dict_map={"PDT_MODELO": modelo},
    callback_sucesso=ao_contabilizar,
)
```

### 1.5. Consultas e impressão de GR

```python
# Relatório do FlexVision via API (retorna um DataFrame)
df_relatorio = siafe.consulta_flexvision(id="12345", parametros_consulta="2025,123456")

# Imprime em PDF todas as GRs do DataFrame (após gerar_documento preencher num_documento)
siafe.consultar_GR_numDoc(df=df, callback_sucesso=lambda id: print(f"PDF gerado: id={id}"))

# Localiza e imprime uma GR pelo valor (e, opcionalmente, pela data de recolhimento)
num_doc = siafe.consultar_GR_valor(
    valor_pesquisa=1500.00,
    versaoSiafe=1,
    data_pagamento="01/07/2025",
)
```

---

## 2. seilibrary — SEI

A classe `SEI` automatiza o Sistema Eletrônico de Informações: navegação em processos, análise de documentos, despachos, anexos e gestão de marcadores.

### 2.1. Login e controle de processos

```python
from jupiter import SEI

sei = SEI()
sei.logar_sei(usuario="joao.silva", senha="senha", orgao="ORGAO")
sei.trocar_unidade("SUPCONC")

# Retorna todos os processos do Controle de Processos, com todas as colunas
# (processo, atribuicao, tipo, marcadores, controle_prazo, etc.)
processos = sei.controlar_processos()

for p in processos:
    print(p["processo"], "|", p["tipo"], "|", p["marcadores"])
```

### 2.2. Marcadores

```python
# Lista todos os processos de um marcador específico
todos = sei.visualizar_processos_por_marcador(marcador="Aguardando Despacho")

# Filtra localmente os processos que possuem determinado marcador
pendentes = sei.filtrar_processos_por_marcador(todos, marcador="Aguardando Despacho")

# Move um processo de um marcador para outro
sei.pesquisar_processo("0012345-67.2025.8.19.0000")
sei.remover_marcador("0012345-67.2025.8.19.0000")
sei.adicionar_marcador(marcador="Concluído", processo="0012345-67.2025.8.19.0000", flag_removido=True)
```

> `flag_removido=True` evita reabrir o processo quando `adicionar_marcador` é chamado logo após `remover_marcador`.

### 2.3. Análise de documentos

`analisar_documentos` percorre a árvore do processo e retorna um dicionário `{nome_documento: texto}`. Sobre esse resultado, três métodos aplicam diferentes critérios de busca:

| Método | Critério | Retorno |
|---|---|---|
| `verificar_despacho` | **OR** (qualquer termo) | lista de documentos |
| `verificar_despacho_estrito` | **AND** (todos os termos) | lista de documentos |
| `verificar_despacho_detalhado` | quais termos por documento | `{documento: [termos]}` |

```python
sei.pesquisar_processo("0012345-67.2025.8.19.0000")
sei.expandir_pastas()

# Analisa apenas documentos cujo nome contém "Memorando"
documentos = sei.analisar_documentos(condicao="Memorando")

# Documentos que mencionem "pagamento" E "autorizado"
com_ambos = sei.verificar_despacho_estrito(documentos, termos_busca=["pagamento", "autorizado"])

# Mapa detalhado: quais termos foram achados em cada documento
mapa = sei.verificar_despacho_detalhado(
    documentos, termos_busca=["pagamento", "autorizado", "pendente"]
)
for doc, termos in mapa.items():
    print(f"{doc}: {termos}")
```

### 2.4. Download de documentos

`baixar_documento` localiza um documento pelo nome na árvore e salva o PDF localmente — funciona tanto para documentos externos quanto para documentos natos do SEI.

```python
sei.pesquisar_processo("0012345-67.2025.8.19.0000")
sei.expandir_pastas()

if sei.baixar_documento(nome_documento="Balancete Julho 2025"):
    print("Documento baixado com sucesso.")
```

### 2.5. Anexos, despachos e blocos de assinatura

```python
sei.pesquisar_processo("0012345-67.2025.8.19.0000")

# Inclui um arquivo externo como Anexo
sei.incluir_anexo(
    nome_arvore="Balancete Julho 2025",
    caminho_arquivo="C:/Users/joao/Downloads/balancete_jul25.pdf",
    nivel_acesso="publico",   # ou "restrito" (exige hipotese_legal)
)

# Cria um Despacho a partir de um texto padrão cadastrado no SEI.
# Retorna True somente se o despacho for confirmado na árvore do processo.
if sei.incluir_despacho(texto_padrao="Encaminhamento SUPCONC"):

    # Preenche as variáveis do modelo no editor CKEditor5 do SEI
    numero_gr = sei.copiar_informacoes_documento()   # nº do último documento da árvore
    sei.formatar_despacho(
        titulo="Senhor Coordenador,",
        valor="1.500,00",
        valor_por_extenso="um mil e quinhentos reais",
        data="01/07/2025",
        num_documento=numero_gr,
        index_doc=numero_gr,
    )

    # Encaminha o processo para assinatura
    sei.incluir_processo_bloco(bloco="Bloco Assinatura Mensal")
```

---

## 3. apipoint — SharePoint e Microsoft Graph

### 3.1. SharePoint

A classe `SharePoint` gerencia arquivos e pastas de um site corporativo. A autenticação usa cookies do navegador, coletados automaticamente via `automaweb` na primeira conexão e persistidos em disco.

```python
from jupiter import SharePoint

sp = SharePoint(site_url="https://tenant.sharepoint.com/sites/SetorContabil")

# Arquivos
sp.download_arquivo(
    caminho_sharepoint="/sites/SetorContabil/Shared Documents/Relatorios/balancete.xlsx",
    pasta_local="C:/Users/joao/Downloads",
)
sp.upload_arquivo(
    caminho_local="C:/Users/joao/Downloads/resultado_julho.xlsx",
    pasta_sharepoint="/sites/SetorContabil/Shared Documents/Resultados",
)

# Pastas (recursivo nos dois sentidos)
sp.download_pasta("/sites/SetorContabil/Shared Documents/Backup", "C:/Users/joao/Backup_SP")
sp.upload_pasta("C:/Users/joao/Relatorios/2025", "/sites/SetorContabil/Shared Documents/2025")

# Verificações e estrutura
if not sp.existe_pasta("/sites/SetorContabil/Shared Documents/2025/Julho"):
    sp.criar_pasta("/sites/SetorContabil/Shared Documents/2025/Julho")

if sp.existe_arquivo("/sites/SetorContabil/Shared Documents/2025/Junho/temp.xlsx"):
    sp.excluir_arquivo("/sites/SetorContabil/Shared Documents/2025/Junho/temp.xlsx")

sp.excluir_pasta("/sites/SetorContabil/Shared Documents/2025/Rascunhos")
```

| Método | Ação |
|---|---|
| `download_arquivo` / `upload_arquivo` | Transferência de arquivo único |
| `download_pasta` / `upload_pasta` | Transferência recursiva de pastas |
| `criar_pasta` | Cria a estrutura de pastas (garante o caminho) |
| `existe_arquivo` / `existe_pasta` | Verificação de existência |
| `excluir_arquivo` / `excluir_pasta` | Remoção |

### 3.2. Microsoft Graph (Outlook e Teams)

A classe `GraphAPI` usa autenticação *app-only* (client credentials) para integrar com Outlook e Teams. Requer registro de aplicativo no Azure AD e autorização da TI para a conta corporativa.

```python
from jupiter import GraphAPI

graph = GraphAPI(
    tenant_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    client_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
    client_secret="seu-segredo-aqui",
    conta_corporativa="automacao@orgao.rj.gov.br",
)

# Outlook — envio, resposta e leitura
graph.enviar_email(
    titulo="Rotina de julho concluída",
    mensagem="A contabilização de 47 GRs foi finalizada com sucesso.",
    destinatarios=["joao.silva@orgao.rj.gov.br", "chefia@orgao.rj.gov.br"],
)
mensagens = graph.listar_emails(pasta="Inbox", top=5)
graph.responder_email(id_mensagem="AAMkAGI...", texto_resposta="Recebido, em processamento.")
pastas = graph.listar_pastas()
contatos = graph.obter_contatos()

# Teams — mensagem via webhook
graph.enviar_mensagem(
    webhook_url="https://orgao.webhook.office.com/webhookb2/...",
    mensagem="Contabilização de julho finalizada.",
    destinatarios=["equipe-contabil"],
)
```

#### Notificação de erros e envio de logs

`GraphAPI` também serve como canal de monitoramento das rotinas. `notificar_erro` reporta exceções e `enviar_log` despacha o conteúdo de um arquivo de log — ambos por Teams e/ou Outlook.

```python
try:
    siafe.gerar_documento(funcao=siafe.gerar_GR, df=df, dict_map=regras)
except Exception as e:
    graph.notificar_erro(
        excecao=e,
        contexto="Contabilização de GR — lote de julho",
        webhook_url=WEBHOOK,
        destinatarios=["suporte@orgao.rj.gov.br"],
    )

# Ao final da rotina, envia o log de erros gerado por configurar_log
graph.enviar_log(
    caminho_log=caminho_erros,
    titulo="Log de erros — Rotina de julho",
    destinatarios=["suporte@orgao.rj.gov.br"],
)
```

---

## Referência do `dict_map`

O `dict_map` é o mecanismo central de separação entre regras de negócio e automação. Cada chave corresponde ao valor da coluna `tipo_id` no DataFrame; o valor é um dicionário com os campos que o SIAFE exige para aquele tipo de documento.

```python
regras = {

    # ── GR Orçamentária ──────────────────────────────────────────────────────
    "GR_ORC": {
        "TipoDocumento":             "01 - Arrecadação",         # texto exato do dropdown
        "UG":                        "123456",                    # código da UG emitente
        "DomicilioBancario":         "0001",                      # código para pesquisa
        "DomicilioBancarioCompleto": "0001 - BANCO DO BRASIL",    # texto para validação
        "ExtraOrcamentario":         False,

        "IEF":                       "1",
        "Fonte":                     "100",
        "FonteRJ":                   "100",
        "TipoDetalhamentoFonte":     "0",
        "DetalhamentoFonte":         "0000",                      # opcional
        "Convenio":                  "99999",

        "TipoPatrimonial":           "Ativo",
        "ItemPatrimonial":           "Bancos Conta Movimento",
        "OperacaoPatrimonial":       "Entrada de Recursos",
        "NaturezaReceita":           "11180111",                  # exclusivo da GR orçamentária
    },

    # ── GR Extra-orçamentária ────────────────────────────────────────────────
    "GR_EXTRA": {
        "TipoDocumento":             "02 - Extra-orçamentário",
        "UG":                        "654321",
        "DomicilioBancario":         "0002",
        "DomicilioBancarioCompleto": "0002 - CAIXA ECONÔMICA FEDERAL",
        "ExtraOrcamentario":         True,                        # ativa o caminho extra-orçamentário

        "IEF": "1", "Fonte": "100", "FonteRJ": "100",
        "TipoDetalhamentoFonte": "0", "Convenio": "99999",

        "TipoPatrimonial":           "Passivo",
        "ItemPatrimonial":           "Obrigações a Pagar",
        "OperacaoPatrimonial":       "Saída de Recursos",

        "TipoCredor":                "PJ",                        # PJ | PF | CG | UG (padrão: PJ)
        "Credor":                    "12345678000199",
    },

    # ── PDT (Programação de Desembolso de Transferência) ─────────────────────
    "PDT_01": {
        "UG":                               "123456",
        "UGFavorecida":                     "654321",             # opcional; padrão = UG
        "DomicilioBancarioOrigem":          "0001",
        "DomicilioBancarioOrigemCompleto":  "0001 - BANCO DO BRASIL",
        "DomicilioBancarioDestino":         "0002",
        "DomicilioBancarioDestinoCompleto": "0002 - CAIXA",

        "IEF": "1", "Fonte": "100", "FonteRJ": "100",
        "TipoDetalhamentoFonte": "0", "DetalhamentoFonte": "0000", "Convenio": "99999",

        "TipoPatrimonial":                  "Ativo",
        "ItemPatrimonial":                  "Transferências",
        "OperacaoPatrimonial":              "899991",
        "SelecaoPorValor":                  True,                 # seleciona pelo código, não pelo texto

        "Regularizacao":                    "01 - Tipo A",        # opcional
        "JustificativaRegularizacao":       "Regularização do mês anterior",  # opcional
    },

    # ── NP com Inscrição Genérica ────────────────────────────────────────────
    "NP_IG": {
        "UG":                        "123456",
        "TipoPatrimonial":           "Ativo",
        "ItemPatrimonial":           "Ajustes Patrimoniais",
        "OperacaoPatrimonial":       "Bloqueio",
        "SelecaoPorValor":           True,

        "IEF": "1", "Fonte": "100", "FonteRJ": "100",
        "TipoDetalhamentoFonte": "0", "DetalhamentoFonte": "0000",
        "DomicilioBancario":         "0001",

        "InscricaoGenerica":         "12345",                     # opcional
        "TipoInscricaoGenerica":     "01 - Bloqueio",             # opcional
    },

    # ── NA com Estorno ───────────────────────────────────────────────────────
    "NA_ESTORNO": {
        "UG":                        "123456",
        "Estorno":                   True,                        # marca o documento como estorno

        "TipoPatrimonial":           "Ativo",
        "ItemPatrimonial":           "Aplicações Financeiras",
        "OperacaoPatrimonial":       "Resgate",
        "IEF": "1", "Fonte": "100", "FonteRJ": "100",
        "TipoDetalhamentoFonte": "0", "DetalhamentoFonte": "0000",
        "DomicilioBancario":         "0001 - BANCO DO BRASIL",
    },
}
```

### Boas práticas

- Os textos dos dropdowns devem ser **exatamente** iguais aos exibidos no SIAFE (copie diretamente da tela).
- Use `"SelecaoPorValor": True` quando o dropdown tem muitas opções e é mais confiável selecionar pelo código numérico.
- Para PDT via API, o dicionário tem estrutura diferente (JSON de payload) e deve ser obtido com `dicionario_PDT`, não montado manualmente.

---

## Arquivos de XPath

Os módulos `siafelibrary_xpaths.py` e `seilibrary_xpaths.py` centralizam os seletores XPath de cada tela. Quando o SIAFE ou o SEI atualizam a interface, basta ajustar o arquivo `_xpaths.py` correspondente — a lógica de negócio permanece intacta.

**`siafelibrary_xpaths.py`** — por tela do SIAFE:

| Classe | Tela |
|---|---|
| `siafe_xpaths_login` | Login |
| `xpaths_menu` | Menu de navegação |
| `xpaths_gr` | Guia de Recolhimento |
| `xpaths_pde` | PD Extra-orçamentária |
| `xpaths_pdt` | PD de Transferência |
| `xpaths_np` | Nota Patrimonial |
| `xpaths_na` | Nota de Aplicação e Resgate |
| `xpaths_ev` | Nota de Evento |
| `xpaths_consulta` | Filtros de consulta de GR |

**`seilibrary_xpaths.py`** — por área do SEI:

| Classe | Área |
|---|---|
| `sei_xpaths_login` | Login |
| `xpaths_pagina_inicial` | Barra de pesquisa e menu da unidade |
| `xpaths_processos` | Árvore de documentos e iframes |
| `xpaths_documento` | Formulário de inclusão de documento |
| `xpaths_controle_processos` | Tabela do Controle de Processos |

```python
from jupiter import xpaths_gr, siafe_xpaths_login, sei_xpaths_login

print(xpaths_gr.btn_inserir_gr)      # '//*[@id="pt1:tblGuiaRecolhimento:btnInsert"]'
print(siafe_xpaths_login.usuario)    # '//*[@id="loginBox:itxUsuario::content"]'
print(sei_xpaths_login.btn_acessar)  # '//*[@id="sbmAcessar"]'
```

### Estendendo a biblioteca

```python
from jupiter import Siafe, xpaths_gr

class SiafeCustom(Siafe):
    def verificar_numero_gr(self) -> str | None:
        """Retorna o número da GR exibido na tela atual."""
        if self.verifica_visivel(xpaths_gr.numero_documento):
            return self.obter_texto(xpaths_gr.numero_documento)
        return None
```

---

## Arquitetura e decisões de design

- **Separação de regras de negócio.** `Siafe` não contém nenhum código de conta, fonte ou UG. Tudo é injetado via `dict_map`, permitindo que a mesma biblioteca atenda a diferentes sub-setores sem alterações no fonte.
- **Validação antes de prosseguir.** Após cada preenchimento crítico, o robô relê o campo do SIAFE para confirmar que o valor foi aceito. Se a validação falhar, ele aborta o documento (clicando em "Voltar") e tenta novamente — até 3 tentativas por lançamento.
- **Resiliência a falhas de DOM.** Os métodos de interação têm retry automático para `StaleElementReferenceException` e aguardam o cursor sair do estado de carregamento antes de prosseguir.
- **Persistência incremental.** `gerar_documento` invoca `callback_sucesso` a cada documento gerado. Se o processo cair na metade, os documentos já contabilizados estão salvos e apenas os pendentes são reprocessados.
- **Dois caminhos para o SIAFE.** Operações podem ser feitas pela interface web (robusta, cobre todos os documentos) ou pela API REST (mais rápida, disponível para PDT e consultas), conforme a necessidade.
- **XPaths centralizados.** Os seletores ficam nos arquivos `_xpaths.py`, isolando a manutenção das mudanças de interface do SIAFE e do SEI.
- **Observabilidade integrada.** `configurar_log` padroniza os logs de todos os módulos, e `GraphAPI.notificar_erro` / `enviar_log` levam falhas e relatórios para o Teams e o Outlook.
