Metadata-Version: 2.4
Name: botdtp
Version: 0.5.0
Summary: HTTP automation package for DTP and DAMSP portals.
Author: Italhub
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: requests>=2.32.0
Requires-Dist: beautifulsoup4>=4.12.0
Requires-Dist: lxml>=5.2.0
Requires-Dist: pillow>=10.0.0
Requires-Dist: langchain-core>=0.3.0
Requires-Dist: langchain-openai>=0.2.0
Requires-Dist: validate-docbr>=1.11.1
Requires-Dist: xhtml2pdf>=0.2.17
Dynamic: license-file

# botdtp

Pacote de automacao para os portais SGPT e DAMSP da Prefeitura de Sao Paulo.

## O que o projeto entrega

- `SGPTBot`: login, alteracao de senha, comprovante de inspecao, aprovacao/reprovacao e relatorios.
- `DAMSPBot`: fluxos CPF/CNPJ, emissao de licenca PF/PJ e guia PF/PJ.
- `HTTPClient` + `HTTPConfig`: retry, timeout, proxy, sleep entre requests, sessao e echo de payloads.
- Persistencia de sessao em arquivo (`SessionStore`) com TTL (SGPT).
- Janela de acesso configuravel por dia/hora/fuso (SGPT).

## Arquitetura (serie 0.5.x)

A arquitetura foi separada por camadas para facilitar manutencao, testes e evolucao:

- `botdtp.core`: contrato central `ResultadoOperacao`, erros compartilhados e politica de acesso SGPT.
- `botdtp.sgpt`: autenticacao, operacoes de inspecao e relatorios do SGPT.
- `botdtp.damsp`: fluxos CPF/CNPJ, regras de servico/modalidade e resolucao de captcha.
- `http_ops`: transporte HTTP generico (session, retry, timeout, proxy e persistencia opcional).
- `aspx_ops`: operacoes ASPX reutilizaveis (`__VIEWSTATE`, postback, timeline e navegacao).

Essa separacao permite evoluir fluxos de negocio sem acoplar com detalhes de transporte e WebForms.

## Requisitos

- Python `>=3.11`

## Instalacao

```bash
pip install botdtp
```

## Dependencias

Dependencias diretas do pacote (em `pyproject.toml`):

- `requests>=2.32.0`
- `beautifulsoup4>=4.12.0`
- `lxml>=5.2.0`
- `pillow>=10.0.0`
- `langchain-core>=0.3.0`
- `langchain-openai>=0.2.0`
- `validate-docbr>=1.11.1`
- `xhtml2pdf>=0.2.17`

## Contrato padrao de retorno

Toda operacao retorna `ResultadoOperacao`:

- `ok: bool`
- `mensagem: str`
- `data: dict`

Comportamento em falha:

- Operacoes publicas de `Bot` e `Client` retornam `ResultadoOperacao(ok=False, ...)`.
- Nao ha propagacao de excecao para o chamador nesse contrato padrao.
- Em erros, `data` inclui `operacao`, `classe` e `detalhe`.

## Uso de alto nivel (recomendado)

### SGPTBot

```python
from botdtp import SGPTBot
from botdtp.sgpt import (
    SGPTLoginInput,
    SGPTAlterarSenhaInput,
    SGPTComprovanteInspecaoInput,
    SGPTAprovacaoEscolarAnualInput,
    SGPTAprovacaoEscolarSemestralInput,
    SGPTAprovacaoTaxiInput,
    SGPTReprovacaoEscolarAnualInput,
    SGPTReprovacaoEscolarSemestralInput,
    SGPTReprovacaoTaxiInput,
    SGPTItemReprovacaoInput,
    SGPTRelatorioInput,
)

login = SGPTLoginInput(
    codigo_empresa="CODIGO_DA_EMPRESA",
    usuario="USUARIO",
    senha="SUA_SENHA",
)

sgpt = SGPTBot(login=login, reautenticar=True)

sgpt.autenticar(login)

sgpt.alterar_senha(
    SGPTAlterarSenhaInput(
        codigo_empresa="0035",
        usuario="123456",
        senha_atual="SENHA_ATUAL",
        nova_senha="SENHA_NOVA",
        confirmacao_nova_senha="SENHA_NOVA",
    )
)

sgpt.gerar_comprovante_inspecao(
    SGPTComprovanteInspecaoInput(
        numero_guia="055721",
        ano_guia="2026",
        resultado="APROVADA",
    ),
    destino="tmp",
    nome_arquivo="comprovante_guia_055721.pdf",
    echo_post=True,
    echo_get=False,
)

sgpt.aprovar_escolar_anual(
    SGPTAprovacaoEscolarAnualInput(
        placa="ABC1234",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        ano_guia="0000",
        numero_guia="00000",
        validade_extintor="00/0000",
        sn_tacografo="S",
    ),
    destino="tmp",
    nome_arquivo="comprovante_escolar_anual.pdf",
)

sgpt.aprovar_escolar_semestral(
    SGPTAprovacaoEscolarSemestralInput(
        placa="ABC1D23",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        validade_extintor="12/2026",
        sn_tacografo="S",
    ),
    destino="tmp",
    nome_arquivo="comprovante_escolar_semestral.pdf",
)

sgpt.aprovar_taxi(
    SGPTAprovacaoTaxiInput(
        placa="ABC1234",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        ano_guia="2026",
        numero_guia="055721",
        radio_taxi_cod="",
        radio_taxi_nome="",
    ),
    destino_comprovante="tmp",
    nome_arquivo="comprovante_taxi.pdf",
)

itens = [
    SGPTItemReprovacaoInput(
        grupo="X",
        item="X",
        subitem="X",
        valor="X",
        opcao="X",
        descricao="XXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    )
]

sgpt.reprovar_escolar_anual(
    SGPTReprovacaoEscolarAnualInput(
        placa="ABC1234",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        ano_guia="2026",
        numero_guia="055721",
        itens_reprovacao=itens,
    )
)

sgpt.reprovar_escolar_semestral(
    SGPTReprovacaoEscolarSemestralInput(
        placa="ABC1D23",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        itens_reprovacao=itens,
    )
)

sgpt.reprovar_taxi(
    SGPTReprovacaoTaxiInput(
        placa="ABC1234",
        licenca="12345678",
        inspetor_codigo="111",
        inspetor_nome="INSPETOR",
        rt_codigo="222",
        rt_nome="RESPONSAVEL TECNICO",
        ano_guia="2026",
        numero_guia="055721",
        itens_reprovacao=itens,
    )
)

sgpt.gerar_relatorio_inspecoes(
    SGPTRelatorioInput(
        mes=5,
        ano=2026,
        modalidade="E",  # C, E, F, T ou M
        resultado="",     # "", A, P ou R
    ),
    destino="tmp",
    nome_arquivo="relatorio_sgpt_maio_2026",
    salvar_html=True,
)
```

Retorno das aprovacoes SGPT:

```python
ResultadoOperacao(
    ok=True,
    mensagem="Inspeção escolar semestral concluída com sucesso.",
    data={
        "arquivo": "inspecao.pdf",
        "caminho_arquivo": "C:/destino/inspecao.pdf",
        "data": {
            "licenca": "03414100",
            "placa": "CZZ1700",
            "modalidade": "ESCOLAR",
            "periodicidade": "SEMESTRAL",
            "resultado": "APROVADA",
            "inspetor": {"codigo": "3903967", "nome": "NOME DO INSPETOR"},
            "responsavel_tecnico": {"codigo": "5316804", "nome": "NOME DO RT"},
            "validade_extintor": "08/2026",
            "numero_tacografo": "00852092",
        },
    },
)
```

Quando a aprovacao nao gera comprovante, `arquivo` e `caminho_arquivo` retornam `None`. Nas reprovacoes, os dados normalizados ficam em `data["data"]`, incluindo a lista completa em `itens_reprovacao` e sua contagem em `itens_enviados`.

As URLs do portal SGPT sao resolvidas internamente pelo modulo `botdtp.sgpt.params`.

Politica de autenticacao SGPT:

- Login obrigatorio: passe `login=SGPTLoginInput(...)` ao instanciar o bot.
- Manual: use `sgpt.autenticar(...)` para renovar explicitamente a sessao.
- Gerenciada: com `reautenticar=True`, o bot tenta reautenticar ao detectar `SGPTAutenticacaoError`.
- Sessao: `SGPTBot` restaura/salva cookies via `SessionStore`.
- Echo HTTP: todos os metodos operacionais aceitam `echo_post` e `echo_get`; uma reautenticacao executada durante a operacao usa as mesmas flags.
- Login inicial: para rastrear a autenticacao feita no construtor, configure `HTTPConfig(echo_post=..., echo_get=...)`.

### DAMSPBot

```python
from botdtp import DAMSPBot
from botdtp.damsp import (
    DAMSPLicencaPFInput,
    DAMSPGuiaPFInput,
    DAMSPLicencaPJInput,
    DAMSPGuiaPJInput,
    DAMSPModalidadeVeiculoEnum,
    DAMSPServicoEscolarEnum,
    DAMSPServicoTaxiEnum,
    DAMSPOpenAICaptchaSolver,
)

damsp = DAMSPBot(
    captcha_solver=DAMSPOpenAICaptchaSolver(api_key="SUA_OPENAI_KEY"),
    max_tentativas=3,
)

# 1) Iniciar fluxo CPF
damsp.iniciar_fluxo_cpf(
    DAMSPLicencaPFInput(
        cpf="529.982.247-25",
        licenca="123.456-78",
        modalidade=DAMSPModalidadeVeiculoEnum.TAXI,
    )
)

# 2) Iniciar fluxo CNPJ
damsp.iniciar_fluxo_cnpj(
    DAMSPLicencaPJInput(
        cpf_responsavel="529.982.247-25",
        numero_empresa="123456",
        licenca="123.456-78",
        modalidade=DAMSPModalidadeVeiculoEnum.ESCOLAR,
    )
)

# 3) Gerar licenca PF
damsp.gerar_licenca_pf(
    DAMSPLicencaPFInput(
        cpf="529.982.247-25",
        licenca="123.456-78",
        modalidade=DAMSPModalidadeVeiculoEnum.TAXI,
    ),
    destino="tmp",
)

# 4) Gerar licenca PJ
damsp.gerar_licenca_pj(
    DAMSPLicencaPJInput(
        cpf_responsavel="529.982.247-25",
        numero_empresa="123456",
        licenca="123.456-78",
        modalidade=DAMSPModalidadeVeiculoEnum.ESCOLAR,
    ),
    destino="tmp",
)

# 5) Gerar guia PF
damsp.gerar_guia_pf(
    DAMSPGuiaPFInput(
        cpf="529.982.247-25",
        licenca="123.456-78",
        codigo_servico=DAMSPServicoTaxiEnum.RENOVACAO_ALVARA,
        modalidade=DAMSPModalidadeVeiculoEnum.TAXI,
    ),
    destino="tmp",
)

# 6) Gerar guia PJ
damsp.gerar_guia_pj(
    DAMSPGuiaPJInput(
        cpf_responsavel="529.982.247-25",
        numero_empresa="123456",
        licenca="123.456-78",
        codigo_servico=DAMSPServicoEscolarEnum.RENOVACAO_CRM,
        modalidade=DAMSPModalidadeVeiculoEnum.ESCOLAR,
    ),
    destino="tmp",
)
```

Retorno DAMSP de guias:

```python
ResultadoOperacao(
    ok=True,
    mensagem="Guia do DAMSP para PF gerada com sucesso.",
    data={
        "arquivo": "guia_01024700.pdf",
        "caminho_arquivo": "C:\\Users\\...\\guia_01024700.pdf",
        "data": {
            "licenca": "01024700",
            "marca_modelo": "CITROEN/JUMP GREENCAR ES",
            "placa": "FJP5G96",
            "validade": "2026-06-19",
            "situacao": "ATIVO",
        },
        "servico": {
            "codigo": "712",
            "radio_value": "a1",
            "descricao": "RENOVACAO C.R.M.",
        },
        "reemissao": False,
    },
)
```

Nas operacoes que geram PDF, `arquivo` contem somente o nome do arquivo e `caminho_arquivo` contem o caminho absoluto salvo em disco.

## Uso de baixo nivel (infra)

```python
import requests

from botdtp import HTTPClient, HTTPConfig, DAMSPBot

http = HTTPClient(config=HTTPConfig(), session=requests.Session())
damsp = DAMSPBot(http=http)
```

Nos metodos publicos de `SGPTBot` e `DAMSPBot`, use `echo_post=True` para imprimir no terminal as URLs e payloads enviados por POST. Use `echo_get=True` para imprimir tambem as chamadas GET. Os parametros sao independentes, ficam desligados por padrao e valem somente durante a operacao:

```python
resultado = damsp.gerar_guia_pf(
    payload,
    destino="tmp",
    nome_arquivo="guia_01024700",
    echo_post=True,
    echo_get=False,
)
```

Campos sensiveis conhecidos sao mascarados no log.

## Captcha DAMSP

`botdtp.damsp.captcha` contem:

- `DAMSPOpenAICaptchaSolver` (automatico via OpenAI/LangChain)
- `DAMSPManualCaptchaSolver` (entrada manual)
- `DAMSPOpenAICaptchaConfig`
- `converter_para_bytes_png`

Configuracao recomendada:

- Forneca `api_key` explicitamente em `DAMSPOpenAICaptchaSolver`.
- Use `max_tentativas` no `DAMSPBot` para controlar novas tentativas quando o portal recusar o captcha.
- Ajuste preset do modelo via `DAMSPOpenAICaptchaConfig(model=..., max_tokens=..., temperature=...)`.
- Se usar env, resolva no bootstrap da aplicacao e injete os valores no construtor.

## Janela de acesso (SGPT) e sessao

Janela padrao: segunda a sabado (`1,2,3,4,5,6`), `07:00` ate `20:00`, fuso `America/Sao_Paulo`.

Personalizacao via codigo:

- Janela: passe `ControleAcessoConfig(...)` ao criar `SGPTBot`.
- Persistencia em arquivo: disponivel no `SGPTBot` via `SessionPersistenceConfig(...)`.
- `DAMSPBot`: sem persistencia em disco; cada operacao refaz o fluxo de identificacao do perfil (PF/PJ).
- HTTP: use `HTTPConfig(...)` com timeout, retry e proxy; em `SGPTBot` e `DAMSPBot`, ligue rastreio por operacao com `echo_post` e `echo_get`.

## Scripts de runtime e testes

Exemplos de runtime:

- `examples/runtime_sgpt_smoke.py`
- `examples/runtime_sgpt_alterar_senha_smoke.py`
- `examples/runtime_sgpt_relatorio_smoke.py`
- `examples/runtime_damsp_licenca_pf_smoke.py`
- `examples/runtime_damsp_licenca_pj_smoke.py`
- `cli/sgpt_menu_cli.py`
- `cli/sgpt_menu_config.example.json`
- `cli/damsp_menu_cli.py`
- `cli/damsp_menu_config.example.json`
- `examples/django_adapter_example.py`

Testes:

```bash
pytest -q
```

## Publicacao da versao 0.5.0

Checklist recomendado para publicar a `0.5.0`:

1. Atualize versao e metadados no `pyproject.toml` (`version = "0.5.0"`).
2. Garanta que este README reflita a API publica atual e a divisao por camadas.
3. Registre nos release notes que `SGPTBot` e `DAMSPBot` expõem rastreio por operacao com `echo_post`/`echo_get`, mantendo as flags desligadas por padrao.
4. Execute a suite local:
   - `pytest -q`
5. Gere os artefatos:
   - `python -m build`
6. Valide distribuicoes:
   - `python -m twine check dist/*`
7. Publique (preferencialmente validando antes no TestPyPI):
   - `python -m twine upload dist/*`
8. Crie tag e release:
   - `git tag v0.5.0`
   - `git push origin v0.5.0`
