Metadata-Version: 2.4
Name: jupiter-subtes
Version: 1.1.1
Summary: Biblioteca para automatização web nos sistemas 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
Dynamic: license-file

# 🪐 jupiter-subtes

**jupiter-subtes** é uma biblioteca Python publicada no PyPI para automatizar tarefas rotineiras do setor contábil do Tesouro do Estado do Rio de Janeiro. Ela unifica, em um único pacote, três domínios de automação: o sistema contábil **SIAFE-Rio2**, o sistema de documentos **SEI** e a integração com o ecossistema **Microsoft 365** (SharePoint e Graph API).

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

---

## Visão Geral dos Módulos

| Módulo | Classe principal | Domínio |
|---|---|---|
| `siafelibrary` | `Siafe` | Automação web do SIAFE-Rio2 (GR, PDE, PDT, NP, NA, EV) |
| `seilibrary` | `SEI` | Automação web do SEI (processos, documentos, despachos) |
| `apipoint` | `SharePoint`, `GraphAPI` | SharePoint (arquivos/pastas) e Microsoft Graph API |

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

---

## 1. siafelibrary — Automação do SIAFE

A classe `Siafe` encapsula toda a interação com o SIAFE-Rio2. Ela gerencia o navegador automaticamente (herda de `automaweb.Navegador`) e expõe métodos de alto nível para login, geração de documentos em lote e consultas.

### Documentos suportados

| Método | Tipo de 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 |

### Exemplo completo: geração de GRs em lote

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

# 1. DataFrame com os lançamentos pendentes
#    Colunas obrigatórias: id, data, valor, observacao, tipo_id
#    Colunas preenchidas pelo robô: num_documento, tempo_contab
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},
])

# 2. Dicionário de regras contábeis (um por tipo_id)
regras = {
    "GR_FUNDO_A": {
        "TipoDocumento":            "01 - Arrecadação",
        "UG":                       "123456",
        "DomicilioBancario":        "0001",
        "DomicilioBancarioCompleto":"0001 - BANCO DO BRASIL S.A.",
        "ExtraOrcamentario":        False,
        "IEF":                      "1",
        "Fonte":                    "100",
        "FonteRJ":                  "100",
        "TipoDetalhamentoFonte":    "0",
        "DetalhamentoFonte":        "0000",
        "Convenio":                 "99999",
        "TipoPatrimonial":          "Ativo",
        "ItemPatrimonial":          "Bancos Conta Movimento",
        "OperacaoPatrimonial":      "Entrada de Recursos",
        "NaturezaReceita":          "11180111",
    },
    "GR_FUNDO_B": {
        # ... mesma estrutura, campos diferentes
    },
}

# 3. Callback chamado a cada documento gerado com sucesso
def ao_contabilizar(id, num_documento, tempo_contab):
    print(f"[OK] id={id}  doc={num_documento}  tempo={tempo_contab}s")
    # Aqui você pode salvar no banco de dados, enviar e-mail, etc.

# 4. Execução
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,
    )
```

### Exemplo: login e consulta via API REST

```python
from jupiter import Siafe

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

# Consulta um relatório do FlexVision pelo ID
df_resultado = siafe.consulta_flexvision(
    id="12345",
    parametros_consulta="2025,123456"  # parâmetros separados por vírgula
)
print(df_resultado.head())
```

### Exemplo: consultar e imprimir GRs por número

```python
# Após gerar_documento, df['num_documento'] já está preenchido.
# Este método abre cada GR no SIAFE e salva o PDF automaticamente.
siafe.consultar_GR_numDoc(df=df, callback_sucesso=lambda id: print(f"PDF gerado para id={id}"))
```

### Ambientes disponíveis (`versaoSiafe`)

| Valor | Ambiente |
|---|---|
| `1` | Produção |
| `2` | Beta / Testes |
| `3` | Homologação |
| `4` | SIAFE-Rio 1 (legado) |

---

## 2. seilibrary — Automação do SEI

A classe `SEI` automatiza o Sistema Eletrônico de Informações (SEI). Ela também herda de `automaweb.Navegador` e oferece métodos para navegar em processos, analisar documentos, incluir despachos e gerenciar blocos de assinatura.

### Exemplo: controle de processos da unidade

```python
from jupiter import SEI

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

# Retorna todos os processos visíveis no Controle de Processos
# com todas as colunas (tipo, atribuicao, marcadores, prazo, etc.)
processos = sei.controlar_processos()

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

### Exemplo: filtrar processos por marcador e incluir despacho

```python
from jupiter import SEI

sei = SEI()
sei.logar_sei(usuario="joao.silva", senha="senha", orgao="SEFAZ")

# Obtém os processos da visualização por marcador
todos = sei.visualizar_processos_por_marcador(marcador="Aguardando Despacho")

# Filtra apenas os que têm o marcador desejado
pendentes = sei.filtrar_processos_por_marcador(todos, marcador="Aguardando Despacho")

for numero in pendentes:
    sei.pesquisar_processo(numero)
    sei.expandir_pastas()

    # Analisa os documentos do processo
    documentos = sei.analisar_documentos(condicao="Memorando")

    # Verifica se algum memorando menciona os termos relevantes
    encontrados = sei.verificar_despacho(documentos, termos_busca=["aprovado", "autorizado"])

    if encontrados:
        # Cria um despacho de encaminhamento
        criado = sei.incluir_despacho(texto_padrao="Encaminhamento Padrão SUPCONC")
        if criado:
            sei.incluir_processo_bloco(bloco="Bloco Assinatura Mensal")
```

### Exemplo: análise detalhada de documentos

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

documentos = sei.analisar_documentos()

# verificar_despacho     → OR  (qualquer um dos termos)
# verificar_despacho_estrito  → AND (todos os termos)
# verificar_despacho_detalhado → quais termos estão em quais documentos

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

# Quero saber quais termos foram encontrados em cada documento
mapa = sei.verificar_despacho_detalhado(
    documentos,
    termos_busca=["pagamento", "autorizado", "pendente", "urgente"]
)
# Resultado: {"Memorando 001/2025": ["PAGAMENTO", "AUTORIZADO"], ...}
for doc, termos in mapa.items():
    print(f"{doc}: {termos}")
```

### Exemplo: incluir anexo e formatar despacho

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

# Inclui um arquivo externo (PDF, planilha, etc.) como Anexo
sei.incluir_anexo(
    nome_arvore="Balancete Julho 2025",
    caminho_arquivo="C:/Users/joao/Downloads/balancete_jul25.pdf",
)

# Inclui um despacho de encaminhamento pelo texto padrão cadastrado no SEI
sei.incluir_despacho(texto_padrao="Encaminhamento SUPCONC")

# Formata o despacho (substitui variáveis no editor CKEditor5 do SEI)
numero_gr = sei.copiar_informacoes_documento()  # obtém número do último documento
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,
)
```

---

## 3. apipoint — Integração com SharePoint e Microsoft 365

### SharePoint

A classe `SharePoint` gerencia arquivos e pastas em um site SharePoint corporativo. A autenticação usa cookies do navegador extraídos automaticamente via `automaweb`.

```python
from jupiter import SharePoint

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

# Download de um arquivo específico
sp.download_arquivo(
    caminho_sharepoint="/sites/SetorContabil/Shared Documents/Relatorios/balancete.xlsx",
    pasta_local="C:/Users/joao/Downloads",
)

# Upload de um arquivo
sp.upload_arquivo(
    caminho_local="C:/Users/joao/Downloads/resultado_julho.xlsx",
    pasta_sharepoint="/sites/SetorContabil/Shared Documents/Resultados",
)

# Download recursivo de uma pasta inteira
sp.download_pasta(
    pasta_sharepoint="/sites/SetorContabil/Shared Documents/Backup",
    pasta_local="C:/Users/joao/Backup_SP",
)

# Verificar existência antes de sobrescrever
caminho = "/sites/SetorContabil/Shared Documents/Resultados/resultado_julho.xlsx"
if not sp.existe_arquivo(caminho):
    sp.upload_arquivo("C:/Users/joao/Downloads/resultado_julho.xlsx",
                      "/sites/SetorContabil/Shared Documents/Resultados")

# Criar estrutura de pastas e limpar arquivos antigos
sp.criar_pasta("/sites/SetorContabil/Shared Documents/2025/Julho")
sp.excluir_arquivo("/sites/SetorContabil/Shared Documents/2025/Junho/temp.xlsx")
```

### GraphAPI (Teams e Outlook)

A classe `GraphAPI` usa autenticação app-only (client credentials) para integrar com Microsoft Teams e Outlook. Requer autorização prévia da TI.

```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_coorporativa="automacao@sefaz.rj.gov.br",
)

# Enviar e-mail
graph.enviar_email(
    titulo="Rotina de Julho concluída",
    mensagem="A contabilização de 47 GRs foi concluída com sucesso.",
    destinatarios=["joao.silva@sefaz.rj.gov.br", "chefia@sefaz.rj.gov.br"],
)

# Listar e-mails não lidos
mensagens = graph.listar_emails(pasta="Inbox", top=5)

# Responder a um e-mail
graph.responder_email(
    id_mensagem="AAMkAGI...",
    texto_resposta="Confirmamos o recebimento. Aguarde processamento.",
)

# Enviar alerta via Teams (webhook)
graph.enviar_mensagem(
    webhook_url="https://sefaz.webhook.office.com/webhookb2/...",
    mensagem="Contabilizacao de julho finalizada.",
    destinatarios=["equipe-contabil"],
)
```

---

## Arquivos XPath (`_xpaths.py`)

Os módulos `siafelibrary_xpaths.py` e `seilibrary_xpaths.py` centralizam os seletores XPath de cada tela do SIAFE e do SEI. São usados internamente pelas classes `Siafe` e `SEI`, mas podem ser acessados diretamente para estender a biblioteca ou inspecionar seletores.

### Por que XPaths separados?

Quando a interface do SIAFE ou do SEI muda (atualização de sistema), basta atualizar o arquivo `_xpaths.py` correspondente — o código de lógica de negócio não precisa ser tocado.

### Estrutura dos arquivos XPath

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

| Classe | Tela |
|---|---|
| `siafe_xpaths_login` | Tela de login |
| `xpaths_menu` | Menu principal 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`** — organizado por área do SEI:

| Classe | Área |
|---|---|
| `sei_xpaths_login` | Tela de 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 |

### Como acessar

```python
# Importação via pacote jupiter (conforme __init__.py)
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"]'

# Importação direta dos módulos (para acesso completo)
from jupiter.siafelibrary_xpaths import xpaths_login  # nome original sem alias
from jupiter.seilibrary_xpaths import xpaths_login
```

### Exemplo: estendendo a biblioteca com um novo comportamento

```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
```

---

## Estruturando o `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 seu DataFrame; o valor é um dicionário com os campos que o SIAFE exige para aquele tipo de documento.

### Referência completa das chaves

```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: 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
        "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: PDT de regularização
        "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",           # código da IG
        "TipoInscricaoGenerica":     "01 - Bloqueio",   # tipo da IG (opcional)
    },

    # ── NA com Estorno ─────────────────────────────────────────────────────
    "NA_ESTORNO": {
        "UG":                        "123456",
        "Estorno":                   True,   # marca a caixa de estorno

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

### Dicas importantes

- Os textos dos dropdowns devem ser **exatamente** iguais aos exibidos no SIAFE (copie direto da tela).
- Use `"SelecaoPorValor": True` quando o dropdown tem muitas opções e é mais confiável selecionar pelo código numérico.
- O DataFrame deve ter pelo menos as colunas: `id`, `data`, `valor`, `observacao`, `tipo_id`, `num_documento` (inicialmente `None`), `tempo_contab` (inicialmente `None`).

---

## Instalação

O módulo `apipoint` (SharePoint e Graph API) depende de uma versão específica do `office365-rest-python-client` que ainda não está publicada no PyPI. **Instale-a manualmente 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 usar o `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 | Biblioteca base de automação web (interna) |
| `pandas` | automática | Manipulação de DataFrames |
| `requests` | automática | Chamadas à API REST do SIAFE |
| `msal` | automática | Autenticação Microsoft (Graph API) |
| `office365-rest-python-client` | **manual** (ver acima) | SharePoint e Graph API |

O Google Chrome deve estar instalado e atualizado. O `automaweb` gerencia o ChromeDriver automaticamente.

---

## Logging

A biblioteca não configura nenhum handler de log por conta própria. Para capturar os erros em arquivo:

```python
import logging

logger = logging.getLogger("jupiter")
handler = logging.FileHandler("automacao.log", encoding="utf-8")
handler.setFormatter(logging.Formatter("%(asctime)s | %(levelname)s | %(message)s"))
logger.addHandler(handler)
logger.setLevel(logging.ERROR)
```

---

## 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 código-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 pelo sistema. Se a validação falhar, ele clica em "Voltar" e tenta novamente (até 3 tentativas por lançamento).
- **Resiliência a falhas de DOM**: todos os métodos de interação com o navegador têm retry automático para `StaleElementReferenceException` e aguardam o cursor sair do estado `wait` antes de prosseguir.
- **XPaths centralizados**: os seletores ficam nos arquivos `_xpaths.py` e não no código de automação, facilitando a manutenção quando o SIAFE ou o SEI atualizam a interface.
- **Callback de sucesso**: `gerar_documento` aceita um `callback_sucesso` chamado a cada documento gerado, permitindo persistência incremental — se o processo cair na metade, os documentos já contabilizados estão salvos.
