"""
Módulo de Automação do SIAFE (siafelibrary.py)
==============================================

Biblioteca desenvolvida para encapsular e automatizar tarefas repetitivas 
no Sistema Integrado de Gestão Orçamentária, Financeira e Contábil (Siafe-Rio2), utilizando a 
biblioteca base `automaweb`.

Esta biblioteca é genérica, focada na interação com a interface web do Siafe 
(cliques, preenchimentos, extração de dados) e não contém regras de negócios 
rígidas (ex: contas, fontes ou IDs específicos). As regras são injetadas nas 
funções via dicionários (`dict_map`).

Classes Principais:
    - Siafe: Herda de `automaweb.Navegador` e fornece os métodos operacionais.
    - xpaths_*: Classes de agrupamento de seletores (XPath) para cada tela do sistema.

Dependências:
    - automaweb
    - pandas
    - selenium
"""

#bibliotecas internas
import automaweb
from .siafelibrary_xpaths import *

#biblioteca pra interface gráfica
from tkinter import messagebox

#bibliotecas pra interação com o driver
from selenium.common.exceptions import NoSuchWindowException
from selenium.common.exceptions import SessionNotCreatedException
from selenium.common.exceptions import InvalidSessionIdException
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys

#bibliotecas apêndice
import time
from datetime import datetime


class Siafe(automaweb.Navegador):
    """
    Classe autônoma para encapsular toda a interação com o Siafe.
    
    Gerencia seu próprio WebDriver (herança) e fornece métodos especializados 
    para navegação, login e geração de documentos contábeis (GR, PD, NA e NP).
    """

    def __init__(self):
        """
        Inicializa a instância do navegador Siafe com um tempo de stun 
        (pausa padrão entre ações) de 0.3 segundos.
        """
        super().__init__(tempo_stun=0.0)
    
        # lista dos métodos pra incluir a espera
        metodos_para_envelopar = ['clicar', 'digitar', 'selecionar_texto', 'limpar']
        
        for nome in metodos_para_envelopar:
            metodo_original = getattr(self, nome)
            setattr(self, nome, self._decorador_espera(metodo_original)) #metodo decorado

    def _decorador_espera(self, func):
        def wrapper(*args, **kwargs):
            res = func(*args, **kwargs)
            self._aguardar_siafe()
            return res
        return wrapper

### USO GENÉRICO ###

    def _voltar(self):
        """
        Clica no botão de "Voltar" padrão do Siafe para retornar à tela de listagem de documentos.
        
        Utilizado principalmente após um erro de preenchimento para abortar 
        a operação atual e tentar a próxima.

        Returns:
            bool: True se o clique foi bem-sucedido, False caso contrário.
            
        Raises:
            Exception: Se houver falha ao localizar ou clicar no botão.
        """
        try:

            #antes de voltar, clicar em botões de cancelar
            botoes_cancelar = self.encontrar_elementos('//*[text()="Cancelar"]')
            num_botoes = len(botoes_cancelar)
            for i in range(num_botoes, 0, -1):
                if i > 2: #evita cancelar a contabilizacao (cancelar apenas os popups de erro)
                    cancelar = self.encontrar_elemento(f'(//*[text()="Cancelar"])[{i-1}]')
                    self.driver.execute_script("arguments[0].click();", cancelar)

            if self.clicar(xpaths_menu.btn_voltar):
                return True
            else:
                return False
        
        except Exception:
            print(f"Erro ao voltar!")
            raise

    def _aguardar_siafe(self):
        """
        Aguarda até que o cursor do mouse (css 'cursor') retorne de 'wait' (carregando) 
        para o estado normal. Previne interações prematuras durante o processamento do servidor.
        """
        self.wait.until(lambda driver: driver.find_element(By.XPATH, '/html/body').value_of_css_property('cursor') != 'wait')

    def _formatar_valor(self, valor):
        '''
        Formata o valor para o padrão brasileiro, para permitir comparação com o valor a ser contabilizado.

        Args:
            valor (str): Valor a ser formatado.

        Returns:
            str: Valor formatado para o padrão brasileiro.
        '''
        valor_float = float(valor.replace(',', '.'))
        valor_formatado = f"{valor_float:,.2f}".replace(',', 'X').replace('.', ',').replace('X', '.')
        return valor_formatado
    
    def _verificar_erro(self, xpath_erro):
        """
        Verifica se houve algum erro na tela e retorna o texto do erro.

        Args:
            xpath_erro (str): XPath para o elemento que contém o texto do erro.

        Returns:
            bool: False se não houver erro, True caso contrário.
        """

        self._aguardar_siafe()
        if self.verifica_visivel(xpath_erro): # Verifica se ocorreu algum erro
            erro = self.obter_texto(xpath_erro)
            if "Erro" in erro:
                erro_body = self.obter_texto(xpath_erro)
                if erro_body and "Saldo insuficiente para contabilização" in erro_body:
                    messagebox.showwarning("Aviso", "Saldo insuficiente. O programa tentará novamente.")
                    self.semsaldo = 1
                self._voltar()
                return True
            else:
                return False
        else:
            return False

    def _digitar_competência(self, xpath_competencia, competencia):

        '''
        Digita a competência no campo de competência do Siafe.

        Args:
            xpath_competencia (str): XPath para o campo de competência do Siafe.
            competencia (str): Competência a ser digitada.
        '''

        competencia_texto = competencia[-7:]
        while self.verificar_texto_digitado(xpath_competencia, competencia_texto) == False:
            try:
                self.digitar(xpaths_pdt.competencia, Keys.ESCAPE)
                self.digitar(xpaths_pdt.competencia, competencia_texto)
            except Exception as e:
                print(f"Erro na competência: {e}")


### ACESSO AO SIAFE ###

    def logar_siafe(self, versaoSiafe, usuario, senha, ano: str = datetime.today().year):
        """
        Abre o navegador, acessa a URL e realiza a autenticação no sistema.

        Args:
            versaoSiafe (int): Define o ambiente (1 para ambiente de produção, 2 para ambiente de testes (Beta)).
            usuario (str): Credencial de acesso (CPF).
            senha (str): Senha de acesso.

        Returns:
            bool: True se o login foi bem-sucedido. False em caso de erro de credenciais 
                  (identificado via leitura do pop-up de erro) ou falha de conexão.
        """
        # Define a URL base dependendo do ambiente escolhido
        if versaoSiafe == 1:
            url = 'https://siafe2.fazenda.rj.gov.br/Siafe/faces/login.jsp'
        if versaoSiafe == 2:
            url = 'http://10.8.237.102:8080/Siafe/faces/login.jsp'
        
        # Abre o navegador instanciado na classe e navega até a URL definida
        self.abrir_url(url)
        try:
            # Preenche os campos de credenciais e clica no botão de entrar
            self.digitar(xpaths_login.usuario, usuario)
            self.digitar(xpaths_login.senha, senha)
            self.selecionar_texto(xpaths_login.ano, str(ano))
            self.clicar(xpaths_login.btn_confirmar)
            
            # Após clicar em confirmar, este bloco tenta ler um possível pop-up de "Usuário e/ou senha incorretos."
            try:
                time.sleep(1) # aguardar_siafe NÃO funciona aqui!
                if self.verifica_visivel(xpaths_login.erro_titulo):
                    erro_titulo = self.obter_texto(xpaths_login.erro_titulo)
                    if erro_titulo and "Erro" in erro_titulo:
                        erro_body = self.obter_texto(xpaths_login.erro_corpo)
                        # Se o pop-up existir e contiver a mensagem, alerta o usuário e interrompe a execução.
                        if erro_body and "Usuário e/ou senha incorretos." in erro_body:
                            messagebox.showwarning("Aviso", "Usuário ou Senha incorretos. Volte a tela de login e tente novamente.")
                            return False   
            except Exception: 
                # Se o elemento NÃO for encontrado, significa que não houve erro de credenciais.
                pass
        
        except Exception:
            # Captura falhas genéricas do WebDriver (ex: página não carregou, elemento não renderizou a tempo) e alerta o usuário sobre a falha de conexão.
            messagebox.showerror("Erro", "Não foi possível logar no SIAFE.")
            return False
        # Se passou por todos os blocos sem retornar False, o login teve sucesso.
        return True  


### USO GENÉRICO ###
    
    def gerar_documento(self, funcao, df, dict_map, callback_sucesso=None):
        """
        Contabiliza os documentos no Siafe seguindo a lógica dos Programas da SUPCONC.

        Args:
            funcao (function): Função que realiza a contabilização. Pode ser gerar_GR, gerar_PDE ou gerar_PDT.
            df (pandas.DataFrame): DataFrame no formato: [data, valor, observacao, num_documento, tipo_id, tempo_contab]
            dict_map (dict): Dicionário que mapeia os campos do DataFrame para os campos do Siafe.
            callback_sucesso (function): Função de callback chamada quando a contabilização foi bem-sucedida.
        """
        
    # REESTRUTURAÇÃO DO DATAFRAME E NAVEGAÇÃO PARA CONTABILIZAÇÃO ---

        df["valor_str"] = df["valor"].astype(str).str.replace(".", ",") # Formata o valor
        df["tentativas"] = 0 # Cria uma coluna de tentativas para controle de falhas

        try: # navega para Documento.
            self.clicar(xpaths_menu.btn_execucao)

            #match/case para navegar para o tipo de execução
            match funcao:
                case self.gerar_NP:
                    self.clicar(xpaths_menu.btn_contabilidade)
                case _:
                    self.clicar(xpaths_menu.btn_execucao_financeira)

            #match/case para navegar para o item desejado
            match funcao:
                case self.gerar_GR:
                    self.clicar(xpaths_gr.btn_gr)
                case self.gerar_PDE:
                    self.clicar(xpaths_pde.btn_pde)
                case self.gerar_PDT:
                    self.clicar(xpaths_pdt.btn_pdt)
                case self.gerar_NP:
                    self.clicar(xpaths_np.btn_np)
                case self.gerar_NA:
                    self.clicar(xpaths_na.btn_na)

        except Exception:
            messagebox.showerror("Erro", f"Não foi possível navegar no menu do SIAFE.")
            return False


    # LOOP DE CONTABILIZAÇÃO (ENQUANTO EXISTIREM LANÇAMENTOS NÃO CONTABILIZADOS) ---

        while df['num_documento'].isna().any():

            for index, row in df[df['num_documento'].isna()].iterrows():

                dict = dict_map.get(row["tipo_id"]) #leitura do respectivo dicionário


            # PREPARO DE DADOS ---

                if row["tentativas"] >= 3:
                    messagebox.showerror(
                        "Erro",
                        f'''O lançamento com ID {row['id']} excedeu o limite de 3 tentativas.
                        Verifique o erro e tente novamente.''')
                    df.loc[index, "tentativas"] += 1 # incrementa o contador de tentativas
                    return False
                
                try:
                    resultado = funcao(
                        data = row['data'],
                        valor = row['valor_str'],
                        observacao = row['observacao'],
                        dicionario = dict
                    )
                    
                    if resultado:
                        num_doc, tempo = resultado
                        df.loc[index, 'num_documento'] = num_doc
                        df.loc[index, 'tempo_contab'] = tempo
                        
                        if callback_sucesso is not None:
                            callback_sucesso(
                                id=row["id"], 
                                num_documento=num_doc, 
                                tempo_contab=tempo
                            )
                    else:
                        continue
                
                except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
                    messagebox.showerror("Erro", "Ocorreu um erro crítico com o navegador.")
                    return False
                
                except Exception as e:
                    messagebox.showerror("Erro", f"Ocorreu um erro inesperado: {e}")
                    return False
                
        return True


### GUIA DE RECOLHIMENTO (GR) ###

    def gerar_GR(self, data, valor, observacao, dicionario):
                
    #  INÍCIO DA CONTABILIZAÇÃO ---

        try:

            self.clicar(xpaths_gr.btn_inserir_gr)
            inicio = time.perf_counter() #cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---

            self.limpar(xpaths_gr.data_emissao)
            self.digitar(xpaths_gr.data_emissao, data)
            self.digitar(xpaths_gr.data_recolhimento, data)
            self.selecionar_texto(xpaths_gr.tipo_documento, dicionario["TipoDocumento"])
            self.digitar(xpaths_gr.ug_emitente, dicionario["UG"])
            self.clicar(xpaths_gr.ug_pesquisar)
            self.digitar(xpaths_gr.domicilio_bancario, dicionario["DomicilioBancario"])
            self.clicar(xpaths_gr.domicilio_bancario_pesquisar)
            if dicionario['ExtraOrcamentario'] == False:
                self.digitar(xpaths_gr.ug_orcamentaria, dicionario["UG"])
                self.clicar(xpaths_gr.ug_orcamentaria_pesquisar)

            # bloco de validação
            validacoes = (
                (self.obter_atributo(xpaths_gr.data_emissao, "value") == data) and
                (self.obter_atributo(xpaths_gr.data_recolhimento, "value") == data) and
                self.verificar_texto_selecionado(xpaths_gr.tipo_documento, dicionario["TipoDocumento"]) and
                (self.obter_atributo(xpaths_gr.ug_emitente, 'value') == dicionario["UG"]) and
                (self.obter_atributo(xpaths_gr.domicilio_bancario, 'value') == dicionario["DomicilioBancarioCompleto"])
            )
            if not validacoes:
                self._voltar()
                return False


        # DETALHAMENTO (COM VALIDAÇÃO) ---

            self.selecionar_texto(xpaths_gr.ief, dicionario["IEF"])
            self.selecionar_texto(xpaths_gr.fonte, dicionario["Fonte"])
            self.selecionar_texto(xpaths_gr.fonte_rj, dicionario["FonteRJ"])
            self.selecionar_texto(xpaths_gr.tipo_detalhamento_fonte, dicionario["TipoDetalhamentoFonte"])
            if dicionario.get("DetalhamentoFonte"):
                self.selecionar_texto(xpaths_gr.detalhamento_fonte, dicionario["DetalhamentoFonte"])
            self.selecionar_texto(xpaths_gr.convenio, dicionario["Convenio"])

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_gr.data_emissao, data) and
                self.verificar_texto_digitado(xpaths_gr.data_recolhimento, data) and
                self.verificar_texto_selecionado(xpaths_gr.tipo_documento, dicionario["TipoDocumento"]) and
                self.verificar_texto_digitado(xpaths_gr.domicilio_bancario, dicionario["DomicilioBancarioCompleto"])
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            # GR Extra-Orçamentária
            if dicionario['ExtraOrcamentario'] == True:

                self.clicar(xpaths_gr.btn_item_extraorcamentario)
                self.clicar(xpaths_gr.btn_inserir_item_extraorcamentario)
                self.selecionar_texto(xpaths_gr.tipo_patrimonial_extra, dicionario["TipoPatrimonial"])
                self.selecionar_texto(xpaths_gr.item_patrimonial_extra, dicionario["ItemPatrimonial"])
                self.selecionar_texto(xpaths_gr.operacao_patrimonial_extra, dicionario["OperacaoPatrimonial"])
                self.selecionar_texto(xpaths_gr.ano_extra, data[-4:])
                self.digitar(xpaths_gr.valor_extra, valor)
                tipo_credor = dicionario.get("TipoCredor", "PJ") # Padrão PJ
                mapa_credor = {
                    "PJ": xpaths_gr.tipo_credor_pj_extra,
                    "CG": xpaths_gr.tipo_credor_cg_extra,
                    "PF": xpaths_gr.tipo_credor_pf_extra,
                    "UG": xpaths_gr.tipo_credor_ug_extra
                }
                if tipo_credor in mapa_credor:
                    self.clicar(mapa_credor[tipo_credor])
                self.digitar(xpaths_gr.credor_extra, dicionario["Credor"])
                self.clicar(xpaths_gr.credor_pesquisar_extra)

                #bloco de validação
                validacoes = (
                    self.verificar_texto_selecionado(xpaths_gr.operacao_patrimonial_extra, dicionario["OperacaoPatrimonial"]) and 
                    self.verificar_texto_digitado(xpaths_gr.valor_extra, self._formatar_valor(valor))
                )
                if not validacoes:
                    self._voltar()
                    return False
                else:
                    self.clicar(xpaths_gr.btn_confirmar_item)

            # GR Orçamentária
            if dicionario['ExtraOrcamentario'] == False:
                
                self.clicar(xpaths_gr.btn_item_orcamentario)
                self.clicar(xpaths_gr.btn_inserir_item_orcamentario)
                self.selecionar_texto(xpaths_gr.tipo_patrimonial_orc, dicionario["TipoPatrimonial"])
                self.selecionar_texto(xpaths_gr.item_patrimonial_orc, dicionario["ItemPatrimonial"])
                self.selecionar_texto(xpaths_gr.operacao_patrimonial_orc, dicionario["OperacaoPatrimonial"]) 
                self.selecionar_texto(xpaths_gr.natureza_receita_orc, dicionario["NaturezaReceita"])
                self._aguardar_siafe()
                self.digitar(xpaths_gr.valor_orc, valor)
                self.digitar(xpaths_gr.valor_orc, Keys.TAB)
                
                # bloco de validação
                validacoes = (
                    self.verificar_texto_selecionado(xpaths_gr.operacao_patrimonial_orc, dicionario["OperacaoPatrimonial"]) and 
                    self.verificar_texto_digitado(xpaths_gr.valor_orc, self._formatar_valor(valor))
                )
                if not validacoes:
                    self._voltar()
                else:
                    self.clicar(xpaths_gr.btn_confirmar_item_orc)


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar(xpaths_gr.btn_inserir_observacao)
            self.digitar(xpaths_gr.observacao, observacao)

            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_gr.observacao, observacao):
                self._voltar()
            else:
                self.clicar(xpaths_gr.btn_contabilizar)
                self.clicar(xpaths_gr.btn_confirmar_contabilizacao)

            # bloco de verificação
            if self._verificar_erro(xpaths_gr.erro):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_gr.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False
        
        else:
            self._voltar()
            return num_documento, tempo_contab


### PROGRAMAÇÃO DE DESEMBOLSO EXTRA-ORÇAMENTÁRIA (PDE) ###
    def gerar_PDE(self, data, valor, observacao, dicionario):
        """
        Contabiliza Programações de Desembolso (PD) Extra-Orçamentárias.

        Args:
            df (pd.DataFrame): Tabela contendo os lançamentos. No formato: 'id', 'data', 'valor', 'observacao', 'tipo_id', 'num_documento' (inicialmente vazio) e 'tempo_contab' (inicialmente vazio).
            dict_map (dict): Dicionário contendo os dados de preenchimento para contabilização. No formato: {tipo_id: {"TipoDocumento": str, "UG": str, "DomicilioBancario": str, "DomicilioBancarioCompleto": str, "IEF": str, "Fonte": str, "FonteRJ": str, "TipoDetalhamentoFonte": str, "DetalhamentoFonte": str, "Convenio": str, "TipoPatrimonial": str, "ItemPatrimonial": str, "OperacaoPatrimonial": str, "NaturezaReceita": str (somente para orçamentário), "TipoCredor": str (PJ, CG, PF ou UG) (somente para extra-orçamentário), "Credor": str}}.
            # CONVERSAR COM O JOÃO A RESPEITO DA LINHA A CIMA! - FORMATO IMPREVISÍVEL
            
            callback_sucesso (callable, optional): Função a ser chamada quando uma PD é gerada com sucesso. 
                                                   Recebe o id, num_documento e tempo_contab.
        
        Returns:
            bool: True se processar a planilha sem erros críticos do WebDriver. 
                  False se exceder tentativas ou ocorrer erro grave.

        Obs:
            Para Regularização: Adicione "Regularizacao": <valor> no dict_map.
        """

    # INÍCIO DA CONTABILIZAÇÃO ---

        try:

            self.clicar(xpaths_pde.btn_inserir_pde)
            inicio = time.perf_counter() # cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---

            self.limpar(xpaths_pde.data_emissao)
            self.digitar(xpaths_pde.data_emissao, data)
            self.clicar(xpaths_pde.data_programacao)
            self.digitar(xpaths_pde.data_programacao, data)
            self.digitar(xpaths_pde.data_vencimento, data)
            self.digitar(xpaths_pde.ug_emitente, dicionario["UG"])
            self.clicar(xpaths_pde.ug_emitente_pesquisar)

            # Lógica de Regularização (via flag "Regularizacao" no dicionário)
            if "Regularizacao" in dicionario:
                if not self.verifica_selecionado(xpaths_pde.ob_regulaziracao):
                    self.clicar(xpaths_pde.ob_regulaziracao)
                self.selecionar_texto(xpaths_pde.regularizacao, dicionario["Regularizacao"])
            else:
                # Garante que o campo de Regularização esteja desmarcado para PDs que não são de Regularização
                if self.verifica_selecionado(xpaths_pde.ob_regulaziracao):
                    self.clicar(xpaths_pde.ob_regulaziracao)

            self.digitar(xpaths_pde.ug_pagadora, dicionario["UG"])
            self.clicar(xpaths_pde.ug_pagadora_pesquisar)
            self.digitar(xpaths_pde.domicilio_bancario_origem, dicionario["DomicilioBancarioOrigem"])
            self.clicar(xpaths_pde.domicilio_bancario_origem_pesquisar)

        # DETALHAMENTO (COM VALIDAÇÃO) ---
            self.selecionar_texto(xpaths_pde.ief, dicionario["IEF"])
            self.selecionar_texto(xpaths_pde.fonte, dicionario["Fonte"])
            self.selecionar_texto(xpaths_pde.fonte_rj, dicionario["FonteRJ"])
            self.selecionar_texto(xpaths_pde.tipo_detalhamento_fonte, dicionario["TipoDetalhamentoFonte"])
            self.selecionar_texto(xpaths_pde.detalhamento_fonte, dicionario["DetalhamentoFonte"])
            self.clicar(xpaths_pde.tipo_credor_pj)
            self.digitar(xpaths_pde.credor_pj, dicionario["Credor"])
            self.clicar(xpaths_pde.credor_pj_pesquisar)
            self.selecionar_texto(xpaths_pde.domicilio_bancario_destino, dicionario["DomicilioBancarioDestino"])
            self._digitar_competência(xpaths_pde.competencia, data)

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_pde.data_emissao, data) and
                self.verificar_texto_digitado(xpaths_pde.data_programacao, data) and
                self.verificar_texto_digitado(xpaths_pde.data_vencimento, data) and
                self.verificar_texto_digitado(xpaths_pde.domicilio_bancario_origem, dicionario["DomicilioBancarioOrigemCompleto"]) and
                self.verificar_texto_selecionado(xpaths_pde.domicilio_bancario_destino, dicionario["DomicilioBancarioDestino"])
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            self.clicar(xpaths_pde.btn_itens)
            self.clicar(xpaths_pde.btn_inserir_item)

            try:
                self.selecionar_texto(xpaths_pde.tipo_patrimonial, dicionario["TipoPatrimonial"])
                self.selecionar_texto(xpaths_pde.item_patrimonial, dicionario["ItemPatrimonial"])
                self.selecionar_texto(xpaths_pde.operacao_patrimonial, dicionario["OperacaoPatrimonial"])
                self.selecionar_texto(xpaths_pde.vinculacao_pagamento, dicionario["VinculacaoPagamento"])
                self.selecionar_texto(xpaths_pde.ano, data[-4:])
                self.digitar(xpaths_pde.valor, valor)
                self.digitar(xpaths_pde.valor, Keys.TAB)

                # bloco de validação
                validacoes = (
                    self.verificar_texto_selecionado(xpaths_pde.operacao_patrimonial, dicionario["OperacaoPatrimonial"]) and
                    self.verificar_texto_digitado(xpaths_pde.valor, self._formatar_valor(valor))
                )
                if not validacoes:
                    raise Exception("Falha na verificação do item (PDE)")

                self.clicar(xpaths_pde.btn_confirmar_item)

            except Exception:
                # Cancela o item se houver erro no preenchimento ou na confirmação, e pula pra próxima tentativa.
                self.clicar(xpaths_pde.btn_cancelar_item)
                self._voltar()
                return False


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar(xpaths_pde.btn_inserir_observacao)
            self.digitar(xpaths_pde.observacao, observacao)
            
            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_pde.observacao, observacao):
                self._voltar()
                return False
            else:
                self.clicar(xpaths_pde.btn_contabilizar)
                self.clicar(xpaths_pde.btn_confirmar_contabilizacao)

            # bloco de verificação
            if self._verificar_erro(xpaths_pde.erro):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_pde.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False
        
        else:
            self._voltar()
            return num_documento, tempo_contab

    
### PROGRAMAÇÃO DE DESEMBOLSO DE TRANSFERÊNCIA (PDT) ###
    def gerar_PDT(self, data, valor, observacao, dicionario):
        """
        Contabiliza Programações de Desembolso (PD) de Transferência.

        Inclui lógicas específicas para:
        - Seleção de Operação Patrimonial via valor ou texto (flag 'SelecaoPorValor').
        - Tratamento especial para domicílios de destino do tipo "BCO AUTENT".
        - Revalidação do campo de competência (devido a bugs da interface do Siafe).

        Args:
            df (pd.DataFrame): Tabela contendo os lançamentos. No formato: 'id', 'data', 'valor', 'observacao', 'tipo_id', 'num_documento' (inicialmente vazio) e 'tempo_contab' (inicialmente vazio).
            dict_map (dict): Dicionário contendo os dados de preenchimento para contabilização. No formato: {tipo_id: {"TipoDocumento": str, "UG": str, "DomicilioBancario": str, "DomicilioBancarioCompleto": str, "IEF": str, "Fonte": str, "FonteRJ": str, "TipoDetalhamentoFonte": str, "DetalhamentoFonte": str, "Convenio": str, "TipoPatrimonial": str, "ItemPatrimonial": str, "OperacaoPatrimonial": str, "NaturezaReceita": str (somente para orçamentário), "TipoCredor": str (PJ, CG, PF ou UG) (somente para extra-orçamentário), "Credor": str}}.
            # CONVERSAR COM O JOÃO A RESPEITO DA LINHA A CIMA! - FORMATO IMPREVISÍVEL
            
            callback_sucesso (callable, optional): Função a ser chamada quando uma PD é gerada com sucesso. 
                                                   Recebe o id, num_documento e tempo_contab.
        
        Returns:
            bool: True se processar a planilha sem erros críticos do WebDriver. 
                  False se exceder tentativas ou ocorrer erro grave.

        Obs:
            Para Regularização: Adicione "Regularizacao": <valor> no dict_map.
            Para Operação Patrimonial por código: Adicione "SelecaoPorValor": True no dict_map.
        """

    # INÍCIO DA CONTABILIZAÇÃO ---

        try:

            self.clicar(xpaths_pdt.btn_inserir_pdt)
            inicio = time.perf_counter() # cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---
            self.limpar(xpaths_pdt.data_emissao)
            self.digitar(xpaths_pdt.data_emissao, data)
            self.clicar(xpaths_pdt.data_programacao)
            self.digitar(xpaths_pdt.data_programacao, data)
            self.digitar(xpaths_pdt.data_vencimento, data)
            self.digitar(xpaths_pdt.ug_emitente, dicionario["UG"])
            self.clicar(xpaths_pdt.ug_emitente_pesquisar)
            ug_favorecida = dicionario.get("UGFavorecida", dicionario["UG"])
            self.digitar(xpaths_pdt.ug_favorecida, ug_favorecida)
            self.clicar(xpaths_pdt.ug_favorecida_pesquisar)
            self.digitar(xpaths_pdt.ug_pagadora, dicionario["UG"])
            self.clicar(xpaths_pdt.ug_pagadora_pesquisar)

            # Lógica de Regularização (via flag "Regularizacao" no dicionário)
            if "Regularizacao" in dicionario:
                if not self.verifica_selecionado(xpaths_pdt.ob_regulaziracao):
                    self.clicar(xpaths_pdt.ob_regulaziracao)
                self.selecionar_texto(xpaths_pdt.regularizacao, dicionario["Regularizacao"])
            else:
                # Garante que o campo de Regularização esteja desmarcado para PDs que não são de Regularização
                if self.verifica_selecionado(xpaths_pdt.ob_regulaziracao):
                    self.clicar(xpaths_pdt.ob_regulaziracao)

            self.digitar(xpaths_pdt.domicilio_bancario_emitente, dicionario["DomicilioBancarioOrigem"])
            self.clicar(xpaths_pdt.domicilio_bancario_emitente_pesquisar)

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_pdt.data_emissao, data) and
                self.verificar_texto_digitado(xpaths_pdt.data_programacao, data) and
                self.verificar_texto_digitado(xpaths_pdt.data_vencimento, data) and
                self.verificar_texto_digitado(xpaths_pdt.domicilio_bancario_emitente, dicionario['DomicilioBancarioOrigemCompleto'])
            )
            if not validacoes:
                self._voltar()
                return False


        # DETALHAMENTO (COM VALIDAÇÃO) ---

            self._aguardar_siafe()
            self.selecionar_texto(xpaths_pdt.ief_origem, dicionario["IEF"])
            self.selecionar_texto(xpaths_pdt.fonte_origem, dicionario["Fonte"])
            self.selecionar_texto(xpaths_pdt.fonte_rj_origem, dicionario["FonteRJ"])
            self.selecionar_texto(xpaths_pdt.tipo_detalhamento_fonte_origem, dicionario["TipoDetalhamentoFonte"])
            self.selecionar_texto(xpaths_pdt.detalhamento_fonte_origem, dicionario["DetalhamentoFonte"])
            self.selecionar_texto(xpaths_pdt.convenio_origem, dicionario["Convenio"])
            self.digitar(xpaths_pdt.domicilio_bancario_favorecida, dicionario["DomicilioBancarioDestino"])
            self.clicar(xpaths_pdt.domicilio_bancario_favorecida_pesquisar)

            # Lógica Exclusiva: Se o domicílio bancário de destino contiver "BCO AUTENT", clicar na aba específica para Banco Autent.
            if "BCO AUTENT" in dicionario.get("DomicilioBancarioDestinoCompleto", ""):
                self._aguardar_siafe(); self.clicar(xpaths_pdt.tab_bco_autent); self.clicar(xpaths_pdt.tab_ok) 

            self._aguardar_siafe()
            self.selecionar_texto(xpaths_pdt.ief_favorecida, dicionario["IEF"])
            self.selecionar_texto(xpaths_pdt.fonte_favorecida, dicionario["Fonte"])
            self.selecionar_texto(xpaths_pdt.fonte_rj_favorecida, dicionario["FonteRJ"])
            self.selecionar_texto(xpaths_pdt.tipo_detalhamento_fonte_favorecida, dicionario["TipoDetalhamentoFonte"])
            self.selecionar_texto(xpaths_pdt.detalhamento_fonte_favorecida, dicionario["DetalhamentoFonte"])
            self.selecionar_texto(xpaths_pdt.convenio_favorecida, dicionario["Convenio"])
            self._digitar_competência(xpaths_pdt.competencia, data)

            if dicionario.get("JustificativaRegularizacao"):
                self.digitar(xpaths_pdt.justificativa_regularizacao, dicionario["JustificativaRegularizacao"])

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_pdt.domicilio_bancario_favorecida, dicionario['DomicilioBancarioDestinoCompleto'])
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            self.clicar(xpaths_pdt.btn_itens)
            self.clicar(xpaths_pdt.btn_inserir_item)

            try:
                self.selecionar_texto(xpaths_pdt.tipo_patrimonial, dicionario["TipoPatrimonial"])
                self.selecionar_texto(xpaths_pdt.item_patrimonial, dicionario["ItemPatrimonial"])

                # Lógica: Seleção por Valor ou Texto (via flag SelecaoPorValor = True no dicionário).
                # Permite a flexibilidade de selecionar a operação patrimonial pelo seu código ou pela seu texto.
                if dicionario.get("SelecaoPorValor") is True:
                    self.selecionar_valor(xpaths_pdt.operacao_patrimonial, dicionario["OperacaoPatrimonial"])
                else:
                    self.selecionar_texto(xpaths_pdt.operacao_patrimonial, dicionario["OperacaoPatrimonial"])

                self._aguardar_siafe()
                self.digitar(xpaths_pdt.valor, valor)
                self.digitar(xpaths_pdt.valor, Keys.TAB)

                # bloco de validação
                validacoes = (
                    self.verificar_texto_selecionado(xpaths_pdt.operacao_patrimonial, dicionario["OperacaoPatrimonial"]) and
                    self.verificar_texto_digitado(xpaths_pdt.valor, self._formatar_valor(valor))
                )
                if not validacoes:
                    raise Exception("Falha na verificação do item (PDT)")

                self.clicar(xpaths_pdt.btn_confirmar_item)

            except Exception:
                # Cancela o item se houver erro no preenchimento ou na confirmação, e pula pra próxima tentativa.
                self.clicar(xpaths_pdt.btn_cancelar_item)
                self._voltar()
                return False


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar(xpaths_pdt.btn_inserir_observacao)
            self.digitar(xpaths_pdt.observacao, observacao)
            
            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_pdt.observacao, observacao):
                self._voltar()
                return False
            else:
                self.clicar(xpaths_pdt.btn_contabilizar)
                self.clicar(xpaths_pdt.btn_confirmar_contabilizacao)
            
            # bloco de verificação
            if self._verificar_erro(xpaths_pdt.erro):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_pdt.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False

        else:
            self._voltar()
            return num_documento, tempo_contab

   
### NOTA PATRIMONIAL (NP) ###
    def gerar_NP(self, data, valor, observacao, dicionario):
        """
        Contabiliza Notas Patrimoniais (NP).

        Inclui lógicas específicas para:
        - Seleção de Operação Patrimonial via valor ou texto (flag 'SelecaoPorValor').
        - Preenchimento de Inscrição Genérica com validação de conclusão (flags 'InscricaoGenerica', 'TipoInscricaoGenerica', 'IGCompleta').

        Args:
            df (pd.DataFrame): Tabela contendo os lançamentos. No formato: 'id', 'data', 'valor', 'observacao', 'tipo_id', 'num_documento' (inicialmente vazio) e 'tempo_contab' (inicialmente vazio).
            dict_map (dict): Dicionário contendo os dados de preenchimento para contabilização. No formato: {tipo_id: {"TipoDocumento": str, "UG": str, "DomicilioBancario": str, "DomicilioBancarioCompleto": str, "IEF": str, "Fonte": str, "FonteRJ": str, "TipoDetalhamentoFonte": str, "DetalhamentoFonte": str, "Convenio": str, "TipoPatrimonial": str, "ItemPatrimonial": str, "OperacaoPatrimonial": str, "NaturezaReceita": str (somente para orçamentário), "TipoCredor": str (PJ, CG, PF ou UG) (somente para extra-orçamentário), "Credor": str}}.
            # CONVERSAR COM O JOÃO A RESPEITO DA LINHA A CIMA! - FORMATO IMPREVISÍVEL
            
            callback_sucesso (callable, optional): Função a ser chamada quando uma NP é gerada com sucesso. 
                                                   Recebe o id, num_documento e tempo_contab.
        
        Returns:
            bool: True se processar a planilha sem erros críticos do WebDriver. 
                  False se exceder tentativas ou ocorrer erro grave.

        Obs:
            Para Operação Patrimonial por código: Adicione "SelecaoPorValor": True no dict_map.
            Para Inscrição Genérica: Adicione "InscricaoGenerica": <valor> (e opcionalmente "TipoInscricaoGenerica" e "IGCompleta") no dict_map.
        """

    # INÍCIO DA CONTABILIZAÇÃO ---

        try:

            self.clicar(xpaths_np.btn_inserir_np)
            inicio = time.perf_counter() # cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---

            self.limpar(xpaths_np.data_emissao)
            self.digitar(xpaths_np.data_emissao, data)
            ug_emitente = dicionario.get("UG")
            if not ug_emitente: self._voltar(); return False
            self.digitar(xpaths_np.ug_emitente, ug_emitente)
            self.clicar(xpaths_np.ug_emitente_pesquisar)

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_np.data_emissao, data) and
                self.verificar_texto_digitado(xpaths_np.ug_emitente, ug_emitente)
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            self.clicar(xpaths_np.btn_inserir_item)

            try:
                self.selecionar_texto(xpaths_np.tipo_patrimonial, dicionario["TipoPatrimonial"])
                self.selecionar_texto(xpaths_np.item_patrimonial, dicionario["ItemPatrimonial"])

                # Lógica: Seleção por Valor ou Texto (via flag SelecaoPorValor = True no dicionário).
                # Permite a flexibilidade de selecionar a operação patrimonial pelo seu código ou pelo seu texto.
                if dicionario.get("SelecaoPorValor") is True:
                    self.selecionar_valor(xpaths_np.operacao_patrimonial, dicionario["OperacaoPatrimonial"])
                else:
                    self.selecionar_texto(xpaths_np.operacao_patrimonial, dicionario["OperacaoPatrimonial"])

                # --- DETALHAMENTO ---
                self.selecionar_texto(xpaths_np.ief, dicionario["IEF"])
                self.selecionar_texto(xpaths_np.fonte, dicionario["Fonte"])
                self.selecionar_texto(xpaths_np.fonte_rj, dicionario["FonteRJ"])
                self.selecionar_texto(xpaths_np.tipo_detalhamento_fonte, dicionario["TipoDetalhamentoFonte"])
                self.selecionar_texto(xpaths_np.detalhamento_fonte, dicionario["DetalhamentoFonte"])
                self.selecionar_texto(xpaths_np.ano, data[-4:])
                self.selecionar_texto(xpaths_np.domicilio_bancario, dicionario["DomicilioBancario"])
                self.digitar(xpaths_np.valor, valor)

                # Lógica de Inscrição Genérica (Bloqueio/Desbloqueio)
                if "InscricaoGenerica" in dicionario:
                    self._aguardar_siafe()
                    if "TipoInscricaoGenerica" in dicionario:
                        self.selecionar_texto(xpaths_np.tipo_inscricao_generica, dicionario["TipoInscricaoGenerica"])
                    self.digitar(xpaths_np.inscricao_generica, dicionario["InscricaoGenerica"])
                    self.clicar(xpaths_np.valor) # Clica fora para validar

                # Validação Inscrição Genérica Completa
                if dicionario.get("InscricaoGenerica"):
                    for _ in range(10):
                        elem_value = self.obter_atributo(xpaths_np.inscricao_generica, "value")
                        elem_title = self.obter_atributo(xpaths_np.inscricao_generica, "title")
                        if dicionario["InscricaoGenerica"] in (str(elem_value), str(elem_title)):
                            break
                        self._aguardar_siafe()               
                self._aguardar_siafe()
                self.clicar(xpaths_np.btn_confirmar_item)

            except Exception:
                # Cancela o item se houver erro no preenchimento ou na confirmação, e pula pra próxima tentativa.
                self.clicar(xpaths_np.btn_cancelar_item)
                self._voltar()
                return False


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar(xpaths_np.btn_inserir_observacao)
            self.digitar(xpaths_np.observacao, observacao)
            
            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_np.observacao, observacao):
                self._voltar()
                return False
            else:
                self.clicar(xpaths_np.btn_contabilizar)
                self.clicar(xpaths_np.btn_confirmar_contabilizacao)
            
            # bloco de verificação
            if self._verificar_erro(xpaths_np.erro_titulo):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_np.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False
        
        else:
            self._voltar()
            return num_documento, tempo_contab
    
    def copiar_NP(self,):
        pass
    
### NOTA DE APLICAÇÃO E RESGATE (NA) ###
    def gerar_NA(self, data, valor, observacao, dicionario):
        """
        Contabiliza Notas de Aplicação e Resgate (NA).

        Args:
            df (pd.DataFrame): Tabela contendo os lançamentos. No formato: 'id', 'data', 'valor', 'observacao', 'tipo_id', 'num_documento' (inicialmente vazio) e 'tempo_contab' (inicialmente vazio).
            dict_map (dict): Dicionário contendo os dados de preenchimento para contabilização. No formato: {tipo_id: {"TipoDocumento": str, "UG": str, "DomicilioBancario": str, "DomicilioBancarioCompleto": str, "IEF": str, "Fonte": str, "FonteRJ": str, "TipoDetalhamentoFonte": str, "DetalhamentoFonte": str, "Convenio": str, "TipoPatrimonial": str, "ItemPatrimonial": str, "OperacaoPatrimonial": str, "NaturezaReceita": str (somente para orçamentário), "TipoCredor": str (PJ, CG, PF ou UG) (somente para extra-orçamentário), "Credor": str}}.
            # CONVERSAR COM O JOÃO A RESPEITO DA LINHA A CIMA! - FORMATO IMPREVISÍVEL
            
            callback_sucesso (callable, optional): Função a ser chamada quando uma NA é gerada com sucesso. 
                                                   Recebe o id, num_documento e tempo_contab.
        
        Returns:
            bool: True se processar a planilha sem erros críticos do WebDriver. 
                  False se exceder tentativas ou ocorrer erro grave.

        Obs:
            Para Estorno: Adicione "Estorno": True no dict_map.
        """

    # INÍCIO DA CONTABILIZAÇÃO ---

        try:

            self.clicar(xpaths_na.btn_inserir_na)
            inicio = time.perf_counter() # cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---

            self.limpar(xpaths_na.data_emissao)
            self.digitar(xpaths_na.data_emissao, data)
            self.digitar(xpaths_na.ug_emitente, dicionario["UG"])
            self.digitar(xpaths_na.valor, Keys.TAB)

            # Lógica de Estorno (Via flag Estorno = True no dicionário)
            if dicionario.get("Estorno") is True:
                self.clicar(xpaths_na.estorno)

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_na.data_emissao, data)
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            self.selecionar_texto(xpaths_na.tipo_patrimonial, dicionario["TipoPatrimonial"])
            self.selecionar_texto(xpaths_na.item_patrimonial, dicionario["ItemPatrimonial"])
            self.selecionar_texto(xpaths_na.operacao_patrimonial, dicionario["OperacaoPatrimonial"])
            self.selecionar_texto(xpaths_na.ief, dicionario["IEF"])
            self.selecionar_texto(xpaths_na.fonte, dicionario["Fonte"])
            self.selecionar_texto(xpaths_na.fonte_rj, dicionario["FonteRJ"])
            self.selecionar_texto(xpaths_na.tipo_detalhamento_fonte, dicionario["TipoDetalhamentoFonte"])
            self.selecionar_texto(xpaths_na.detalhamento_fonte, dicionario["DetalhamentoFonte"])
            self.selecionar_texto(xpaths_na.domicilio_bancario_origem, dicionario["DomicilioBancario"])
            self.selecionar_texto(xpaths_na.domicilio_bancario_destino, dicionario["DomicilioBancario"])
            self.digitar(xpaths_na.valor, valor)
            self.digitar(xpaths_na.valor, Keys.TAB)

            # bloco de validação
            validacoes = (
                self.verificar_texto_selecionado(xpaths_na.operacao_patrimonial, dicionario["OperacaoPatrimonial"]) and
                self.verificar_texto_selecionado(xpaths_na.domicilio_bancario_origem, dicionario["DomicilioBancario"]) and
                self.verificar_texto_selecionado(xpaths_na.domicilio_bancario_destino, dicionario["DomicilioBancario"]) and
                self.verificar_texto_digitado(xpaths_na.valor, self._formatar_valor(valor))
            )
            if not validacoes:
                self._voltar()
                return False


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar(xpaths_na.btn_inserir_observacao)
            self.digitar(xpaths_na.observacao, observacao)

            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_na.observacao, observacao):
                self._voltar()
                return False
            else:
                self.clicar(xpaths_na.btn_contabilizar)
                self.clicar(xpaths_na.btn_confirmar_contabilizacao)
            
            # bloco de verificação
            if self._verificar_erro(xpaths_na.erro_titulo):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_na.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False
        
        else:
            self._voltar()
            return num_documento, tempo_contab


### NOTA DE EVENTO (NE) ###

    def gerar_EV(self, data, observacao, df, dicionario):

    # INÍCIO DA CONTABILIZAÇÃO ---

        df["valor_str"] = df["valor"].astype(float).round(2)
        df["valor_str"] = df["valor_str"].astype(str).str.replace(".", ",") # Formata o valor

        try:

            self.clicar_script(xpaths_ev.btn_inserir_ev)
            inicio = time.perf_counter() # cronômetro


        # IDENTIFICAÇÃO (COM VALIDAÇÃO) ---

            self.limpar(xpaths_ev.data_emissao)
            self.digitar_script(xpaths_ev.data_emissao, data)
            self.digitar_script(xpaths_ev.ug_emitente, dicionario.get("UG"))
            self.clicar_script(xpaths_ev.ug_emitente_pesquisar)

            # bloco de validação
            validacoes = (
                self.verificar_texto_digitado(xpaths_ev.data_emissao, data) and
                self.verificar_texto_digitado(xpaths_ev.ug_emitente, dicionario.get("UG"))
            )
            if not validacoes:
                self._voltar()
                return False


        # PREENCHIMENTO DO ITEM (COM VALIDAÇÃO) ---

            df['tentativas'] = 0
            df['status_item'] = None

            while df['status_item'].isnull().any():

                for index, row in df[df['status_item'].isnull()].iterrows():

                    if row["tentativas"] >= 3:
                        messagebox.showerror(
                            "Erro",
                            f'''O lançamento com ID {row['id']} excedeu o limite de 3 tentativas.
                            Verifique o erro e tente novamente.''')
                        return False
                    df.loc[index, "tentativas"] += 1 # incrementa o contador de tentativas
                    
                    try:
                        
                        self.clicar_script(xpaths_ev.btn_inserir_item)

                        self.digitar_script(xpaths_ev.evento, row['evento'])
                        self.clicar_script(xpaths_ev.evento_pesquisar)

                        self.selecionar_texto(xpaths_ev.identificador_exercicio_fonte, dicionario["IEF"])
                        

                        self.digitar_script(xpaths_ev.credor, row['credor'])
                        self._proximo_elemento(xpaths_ev.credor)

                        self.digitar_script(xpaths_ev.valor, row['valor_str'])
                        self._proximo_elemento(xpaths_ev.valor)
                        
                        # bloco de validação
                        validacoes = (
                            self.verificar_texto_digitado(xpaths_ev.evento, row['evento']) and
                            (row['credor'] in self.obter_atributo(xpaths_ev.credor, "value")) and
                            (self._formatar_valor(row['valor_str']) == self.obter_atributo(xpaths_ev.valor, "title"))
                        )
                        print()
                        if not validacoes:
                            print('Erro ao preencher o item.')
                            self.clicar_script(xpaths_ev.btn_cancelar_item)
                        else:
                            self.clicar_script(xpaths_ev.btn_confirmar_item)
                            df.loc[index, "status_item"] = True
                    except Exception as e:
                        self.clicar_script(xpaths_ev.btn_cancelar_item)
                        print(f"Erro: {e}.")
                    
                    print(df)


        # FINALIZAÇÃO E CONTABILIZAÇÃO (COM VALIDAÇÃO) ---

            self.clicar_script(xpaths_ev.btn_inserir_observacao)
            self.digitar_script(xpaths_ev.observacao, observacao)
            
            # bloco de validação
            if not self.verificar_texto_digitado(xpaths_ev.observacao, observacao):
                self._voltar()
                return False
            else:
                self.clicar_script(xpaths_ev.btn_contabilizar)
                self.clicar_script(xpaths_ev.btn_confirmar_contabilizacao)
            
            # bloco de verificação
            if self._verificar_erro(xpaths_ev.erro_titulo):
                return False
                

        # ATUALIZAÇÃO DO DATAFRAME (COM VALIDAÇÃO) ---

            num_documento = self.obter_texto(xpaths_ev.numero_documento) #obtém o número da GR
            tempo_contab = str(round(time.perf_counter() - inicio, 2)).replace(".", ",") #calcula o tempo de contabilização


    # EXCEÇÕES/FINALIZAÇÃO ---

        except (NoSuchWindowException, SessionNotCreatedException, InvalidSessionIdException):
            raise

        except Exception:
            self._voltar()
            return False
        
        else:
            self._voltar()
            return num_documento, tempo_contab

    def copiar_EV(self, num_doc, nova_data, nova_obs):
        
        try: # navega para Documento.
            self.clicar_script(xpaths_menu.btn_execucao)
            self.clicar_script(xpaths_menu.btn_contabilidade)
            self.clicar_script("//*[text()='Nota de Evento']")
        except:
            print("Erro ao navegar para Documento.")
            return False

        #adiciona o filtro
        self.clicar_script(xpaths_ev.btn_filtro)
        self.selecionar_texto('//*[@id="pt1:tblDocumento:table_rtfFilter:0:cbx_col_sel_rtfFilter::content"]', 'Número')
        self.selecionar_texto('//*[@id="pt1:tblDocumento:table_rtfFilter:0:cbx_op_sel_rtfFilter::content"]', 'contém')
        self.digitar_script('//*[@id="pt1:tblDocumento:table_rtfFilter:0:in_value_rtfFilter::content"]', num_doc)
        self.clicar_script('//*[@id="pt1:tblDocumento:table_rtfFilter::db"]')
        if self.obter_texto('//*[@id="pt1:tblDocumento:tabViewerDec::db"]/table/tbody/tr[1]/td[1]/span') != num_doc:
            print('erro')
            return False

        #copia a nota de evento
        self.clicar_script('//*[@id="pt1:tblDocumento:tabViewerDec::db"]/table/tbody/tr[1]/td[1]/span')
        self.clicar_script(xpaths_ev.copiar_ev)

        #muda a data e a observacão
        self.limpar(xpaths_ev.data_emissao)
        self.digitar_script(xpaths_ev.data_emissao, nova_data)
        if self.obter_atributo(xpaths_ev.data_emissao, 'value') != nova_data:
            print('erro')
            return False

        self.clicar_script(xpaths_ev.btn_inserir_observacao)
        self.limpar(xpaths_ev.observacao)
        self.digitar_script(xpaths_ev.observacao, nova_obs)
        self.clicar_script('//*[@id="tplSip:slcObservacao"]')
        if self.obter_atributo(xpaths_ev.observacao, 'title') != nova_obs:
            print('erro')
            return False

        #salvar como rascunho
        self.clicar_script('//*[@id="tplSip:btnConfirmar"]')