def executar_fluxo_boleto(ctx: dict, mensagem: str, atendimento_id: int = None) -> dict:
    """Executa o fluxo de emissão de boleto (busca /at + /np) com logs e persistência"""
    
    from datetime import datetime
    
    log("[FLUXO_BOLETO] Executando fluxo de emissão de boleto", "DEBUG")
    log(f"[FLUXO_BOLETO] plano_escolhido_id no contexto: {ctx.get('plano_escolhido_id')}", "INFO")

    # ============================================
    # FUNÇÃO AUXILIAR LOCAL PARA LISTAR PLANOS
    # ============================================
    def listar_planos_para_escolha(planos: list) -> dict:
        """Lista os planos para o cliente escolher"""
        resposta = "Encontrei vários planos ativos neste cadastro. Escolha qual deseja usar:\n\n"
        for i, plano in enumerate(planos, 1):
            nome_plano = plano.get("plano", "Plano")
            endereco = plano.get("endereco_instalacao", "Endereço não informado")
            vencimento = plano.get("dia_vencimento", "?")
            resposta += f"{i} - *{nome_plano}*\n   Endereço: {endereco}\n   Vencimento: dia {vencimento}\n\n"
        resposta += "Digite o número do plano."
        return {"resposta": resposta, "aguardando_escolha_plano": True}
    
    # ============================================
    # VALIDAÇÕES INICIAIS
    # ============================================
    if not ctx.get("id_cliente"):
        return {"resposta": "Preciso identificar seu cadastro primeiro. Por favor, informe seu CPF."}
    
    if not ctx.get("contatos_confirmados"):
        return etapa_exibir_contatos(ctx)
    
    cadastros = ctx.get("ultima_consulta_cpf", [])
    id_cliente = ctx.get("id_cliente")
    
    cad = None
    for c in cadastros:
        if c.get("id_cliente") == id_cliente:
            cad = c
            break
    
    if not cad:
        return {"resposta": "Não foi possível localizar seus dados. Informe seu CPF novamente."}
    
    planos_ativos = filtrar_planos_ativos(cad.get("planos", []))
    
    if not planos_ativos:
        return {"resposta": "Não encontrei planos ativos sujeitos a cobrança no seu cadastro."}
    
    # ============================================
    # TRATAMENTO DE BOLETO EM ANDAMENTO (emitir_boleto)
    # ============================================
    if ctx.get("aguardando_formato_boleto"):
        opcao = mensagem.strip()
        cob = ctx.get("ultima_cobranca", {})
        
        log(f"[PROCESSAR] Processando escolha de formato do boleto: opcao={opcao}", "DEBUG")
        
        # Opções válidas: 1, 2, 3
        if opcao in ["1", "2", "3"]:
            if opcao == "1":  # Pix
                pix = cob.get('qrcode_copy_paste')
                if not pix:
                    return {"resposta": "Desculpe, não consegui recuperar o código Pix. Tente novamente."}
                ctx["aguardando_formato_boleto"] = False
                ctx["intencao_definida"] = None
                return {
                    "resposta": "Segue o Pix copia e cola do boleto:",
                    "conteudo_adicional": pix,
                    "mensagem_followup": "Deseja mais alguma ajuda?",
                    "acao": "enviar_texto_adicional"
                }
            elif opcao == "2":  # Código de barras
                cod_barras = cob.get('linha_digitavel')
                if not cod_barras:
                    return {"resposta": "Desculpe, não consegui recuperar o código de barras. Tente novamente."}
                ctx["aguardando_formato_boleto"] = False
                ctx["intencao_definida"] = None
                return {
                    "resposta": "Segue o código de barras do boleto:",
                    "conteudo_adicional": cod_barras,
                    "mensagem_followup": "Deseja mais alguma ajuda?",
                    "acao": "enviar_texto_adicional"
                }
            elif opcao == "3":  # PDF
                id_cobranca = cob.get("id_cobranca")
                if not id_cobranca:
                    return {"resposta": "Desculpe, não consegui identificar a cobrança. Tente novamente."}
                resultado = chamar_api_radiusnet("gerar_boleto", id_cobranca=id_cobranca)
                if resultado.get("success"):
                    url_pdf = resultado.get("data")
                    if url_pdf:
                        ctx["aguardando_formato_boleto"] = False
                        ctx["intencao_definida"] = None
                        return {
                            "resposta": "Segue o arquivo PDF do boleto.",
                            "acao": "enviar_documento",
                            "url": url_pdf,
                            "nome": f"boleto_{id_cobranca}.pdf",
                            "mensagem_followup": "Deseja mais alguma ajuda?"
                        }
                return {"resposta": "Não foi possível gerar o PDF. Tente novamente mais tarde."}
        else:
            # Opção não é 1,2,3 – classificar intenção
            log(f"[PROCESSAR] Opção inválida para formato de boleto: {opcao}. Classificando intenção...", "DEBUG")
            intencao = classificar_intencao_global(mensagem)
            log(f"[PROCESSAR] Intenção classificada: {intencao}", "DEBUG")
            
            if intencao == "encerrar":
                from datetime import datetime
                horario = "bom dia" if datetime.now().hour < 12 else "boa tarde" if datetime.now().hour < 18 else "boa noite"
                despedida = MENSAGEM_DESPEDIDA_PADRAO.replace("{horario}", horario) if 'MENSAGEM_DESPEDIDA_PADRAO' in dir() else f"Obrigado por entrar em contato! Tenha um {horario}! 😊"
                ctx["intencao_definida"] = None
                ctx["aguardando_formato_boleto"] = False
                return {"resposta": despedida, "acao": "encerrar"}
            elif intencao == "recomecar":
                for key in list(ctx.keys()):
                    if key not in ['historico', 'protocolo']:
                        del ctx[key]
                ctx["intencao_definida"] = None
                ctx["contatos_confirmados"] = False
                ctx["aguardando_formato_boleto"] = False
                return {"resposta": "Entendido! Vamos recomeçar o atendimento. Por favor, informe seu CPF para continuarmos."}
            elif intencao == "trocar_plano":
                # Limpar plano e cobrança, reiniciar fluxo de boleto
                ctx.pop("plano_escolhido_id", None)
                ctx.pop("ultima_cobranca", None)
                ctx["aguardando_formato_boleto"] = False
                ctx["intencao_definida"] = "emitir_boleto"
                return executar_fluxo_boleto(ctx, mensagem, atendimento_id)
            else:
                # Para outras intenções, manter o estado e pedir opção válida
                return {"resposta": "Para continuar com este boleto, digite 1 para Pix, 2 para código de barras ou 3 para PDF. Ou diga 'trocar plano' para escolher outro plano, 'recomeçar' para reiniciar."}
    
    # ==========================================================
    # TRATAMENTO DA OPÇÃO "VER TODOS OS BOLETOS"
    # ==========================================================
    if ctx.get("aguardando_confirmacao_financeiro_ou_todos"):
        opcao = mensagem.strip()
        if opcao == "1":
            ctx["aguardando_confirmacao_financeiro_ou_todos"] = False
            return executar_fluxo_boleto_todos_planos(ctx, atendimento_id)
        elif opcao == "2":
            ctx["aguardando_confirmacao_financeiro_ou_todos"] = False
            adicionar_fila_humana(atendimento_id, 1)
            conn = get_db()
            cur = conn.cursor()
            cur.execute("UPDATE atendimentos SET status = 'fila', setor_id = 1 WHERE id = %s", (atendimento_id,))
            cur.execute("DELETE FROM fila_robot WHERE atendimento_id = %s", (atendimento_id,))
            conn.commit()
            cur.close()
            conn.close()
            template = MENSAGEM_ENCAMINHAMENTO_HUMANO if 'MENSAGEM_ENCAMINHAMENTO_HUMANO' in dir() else "✅ Entendido! Vou encaminhar seu atendimento para o setor {setor}."
            resposta = template.replace("{setor}", "Financeiro").replace("{protocolo}", ctx.get("protocolo", "N/A"))
            return {"resposta": resposta, "acao": "transferir", "setor_id": 1}
        else:
            return {"resposta": "Opção inválida. Digite 1 para ver boletos de todos os planos ou 2 para falar com o Financeiro."}
    
    # ==========================================================
    # SE JÁ TEM UM PLANO SELECIONADO, USA ELE
    # ==========================================================
    plano_id = ctx.get("plano_escolhido_id")
    
    if plano_id:
        plano_valido = any(p.get("id_cliente_plano") == plano_id for p in planos_ativos)
        if not plano_valido:
            ctx.pop("plano_escolhido_id", None)
            plano_id = None
        else:
            if not ctx.get("aguardando_confirmacao_plano_atual"):
                ctx["aguardando_confirmacao_plano_atual"] = True
                plano_nome = next((p.get("plano") for p in planos_ativos if p.get("id_cliente_plano") == plano_id), "seu plano")
                return {
                    "resposta": f"Você já tem o plano *{plano_nome}* selecionado.\n\n"
                               f"Digite 1 para gerar boleto deste plano.\n"
                               f"Digite 2 para escolher outro plano.\n"
                               f"Digite 3 para ver todos os boletos do seu CPF (todos os planos)."
                }
            else:
                opcao = mensagem.strip()
                if opcao == "1":
                    ctx["aguardando_confirmacao_plano_atual"] = False
                elif opcao == "2":
                    ctx["aguardando_confirmacao_plano_atual"] = False
                    ctx.pop("plano_escolhido_id", None)
                    plano_id = None
                    return listar_planos_para_escolha(planos_ativos)
                elif opcao == "3":
                    ctx["aguardando_confirmacao_plano_atual"] = False
                    return executar_fluxo_boleto_todos_planos(ctx, atendimento_id)
                else:
                    # Opção inválida – classificar intenção
                    log(f"[FLUXO_BOLETO] Opção inválida no menu do plano atual: {opcao}. Classificando intenção...", "DEBUG")
                    intencao = classificar_intencao_global(mensagem)
                    log(f"[FLUXO_BOLETO] Intenção classificada: {intencao}", "DEBUG")
                    
                    if intencao == "encerrar":
                        from datetime import datetime
                        horario = "bom dia" if datetime.now().hour < 12 else "boa tarde" if datetime.now().hour < 18 else "boa noite"
                        despedida = MENSAGEM_DESPEDIDA_PADRAO.replace("{horario}", horario) if 'MENSAGEM_DESPEDIDA_PADRAO' in dir() else f"Obrigado por entrar em contato! Tenha um {horario}! 😊"
                        ctx["intencao_definida"] = None
                        ctx["aguardando_confirmacao_plano_atual"] = False
                        return {"resposta": despedida, "acao": "encerrar"}
                    elif intencao == "recomecar":
                        for key in list(ctx.keys()):
                            if key not in ['historico', 'protocolo']:
                                del ctx[key]
                        ctx["intencao_definida"] = None
                        ctx["contatos_confirmados"] = False
                        ctx["aguardando_formato_boleto"] = False
                        ctx["aguardando_confirmacao_plano_atual"] = False
                        return {"resposta": "Entendido! Vamos recomeçar o atendimento. Por favor, informe seu CPF para continuarmos."}
                    elif intencao == "trocar_plano":
                        ctx.pop("plano_escolhido_id", None)
                        ctx.pop("ultima_cobranca", None)
                        ctx["aguardando_formato_boleto"] = False
                        ctx["aguardando_confirmacao_plano_atual"] = False
                        ctx["intencao_definida"] = "emitir_boleto"
                        return executar_fluxo_boleto(ctx, mensagem, atendimento_id)
                    else:
                        return {"resposta": "Opção inválida. Digite 1 para gerar boleto deste plano, 2 para escolher outro plano ou 3 para ver todos os boletos."}
    
    # ==========================================================
    # SE NÃO TEM PLANO SELECIONADO
    # ==========================================================
    if len(planos_ativos) > 1 and not plano_id:
        if ctx.get("aguardando_escolha_plano"):
            try:
                escolha = int(mensagem.strip())
                if 1 <= escolha <= len(planos_ativos):
                    plano = planos_ativos[escolha - 1]
                    plano_id = plano.get("id_cliente_plano")
                    ctx["plano_escolhido_id"] = plano_id
                    ctx["aguardando_escolha_plano"] = False
                    log(f"[FLUXO_BOLETO] Plano escolhido: {plano_id}", "INFO")
                else:
                    return {"resposta": f"Opção inválida. Escolha entre 1 e {len(planos_ativos)}."}
            except ValueError:
                return {"resposta": "Digite apenas o número do plano."}
        else:
            resposta = "Você tem vários planos ativos. Para qual deseja o boleto?\n"
            for i, p in enumerate(planos_ativos, 1):
                resposta += f"{i} - *{p.get('plano')}* - {p.get('endereco_instalacao', '')}\n"
            resposta += "\nDigite o número do plano.\n\nOu digite 0 para ver todos os boletos de todos os planos."
            ctx["aguardando_escolha_plano"] = True
            ctx["planos_para_escolha"] = planos_ativos
            return {"resposta": resposta}
    
    if not plano_id:
        plano_id = planos_ativos[0].get("id_cliente_plano")
        ctx["plano_escolhido_id"] = plano_id
        log(f"[FLUXO_BOLETO] Único plano, ID salvo: {plano_id}", "INFO")
    
    # ==========================================================
    # BUSCAR COBRANÇAS DO PLANO SELECIONADO
    # ==========================================================
    log(f"[FLUXO_BOLETO] id_cliente_plano = {plano_id}", "INFO")
    
    resultado_at = chamar_api_radiusnet("cobrancas_plano_atraso", id_cliente_plano=plano_id)
    cobrancas_at = resultado_at.get("data", []) if resultado_at.get("success") else []
    log(f"[FLUXO_BOLETO] Cobranças em atraso: {len(cobrancas_at)}", "DEBUG")
    
    resultado_np = chamar_api_radiusnet("cobrancas_plano_nao_pagas", id_cliente_plano=plano_id)
    cobrancas_np = resultado_np.get("data", []) if resultado_np.get("success") else []
    log(f"[FLUXO_BOLETO] Cobranças não pagas: {len(cobrancas_np)}", "DEBUG")
    
    hoje = datetime.now()
    mes_corrente = hoje.month - 1 if hoje.month > 1 else 12
    ano_corrente = hoje.year if hoje.month > 1 else hoje.year - 1
    periodo_corrente = f"{mes_corrente:02d}/{ano_corrente}"
    
    cobranca_corrente = None
    for cob in cobrancas_np:
        if cob.get("periodo_prestacao") == periodo_corrente:
            cobranca_corrente = cob
            break
    
    todas_cobrancas = []
    todas_cobrancas.extend(cobrancas_at)
    if cobranca_corrente:
        todas_cobrancas.append(cobranca_corrente)
    
    if not todas_cobrancas:
        log("[FLUXO_BOLETO] Nenhuma cobrança encontrada.", "INFO")
        ctx["intencao_definida"] = None
        return {
            "resposta": "✅ Não encontrei cobranças pendentes ou a vencer para este plano. Sua conta está em dia!\n\n"
                       "Digite 1 para ver boletos de TODOS os seus planos.\n"
                       "Digite 2 para falar com o Financeiro.",
            "aguardando_confirmacao_financeiro_ou_todos": True
        }
    
    if len(todas_cobrancas) > 1 and not ctx.get("aguardando_escolha_cobranca_emitir"):
        resposta = "Encontrei as seguintes cobranças:\n"
        for i, cob in enumerate(todas_cobrancas, 1):
            periodo = cob.get("periodo_prestacao", "?")
            vencimento = formatar_data(cob.get("data_vencimento"))
            valor = formatar_valor(cob.get("valor_total_a_pagar"))
            status = "⚠️ ATRASADA" if cob in cobrancas_at else "📄 A VENCER"
            resposta += f"{i} - {status} - Período: {periodo} - Vencimento: {vencimento} - Valor: {valor}\n"
        resposta += "\nDigite o número da cobrança que deseja."
        ctx["aguardando_escolha_cobranca_emitir"] = True
        ctx["lista_cobrancas_emitir"] = todas_cobrancas
        return {"resposta": resposta}
    
    if ctx.get("aguardando_escolha_cobranca_emitir"):
        try:
            escolha = int(mensagem.strip())
            cobrancas = ctx.get("lista_cobrancas_emitir", [])
            if 1 <= escolha <= len(cobrancas):
                cob = cobrancas[escolha - 1]
                ctx["ultima_cobranca"] = cob
                ctx["aguardando_escolha_cobranca_emitir"] = False
                ctx["aguardando_formato_boleto"] = True
                vencimento = formatar_data(cob.get("data_vencimento"))
                valor = formatar_valor(cob.get("valor_total_a_pagar"))
                return {
                    "resposta": f"""*Dados da fatura selecionada:*
📅 Vencimento: {vencimento}
💰 Valor: {valor}

Como deseja receber?

1 - Pix (copia e cola)
2 - Código de barras
3 - Boleto em PDF"""
                }
            else:
                return {"resposta": f"Opção inválida. Escolha entre 1 e {len(cobrancas)}."}
        except ValueError:
            return {"resposta": "Digite apenas o número da cobrança."}
    
    cob = todas_cobrancas[0]
    ctx["ultima_cobranca"] = cob
    ctx["aguardando_formato_boleto"] = True
    vencimento = formatar_data(cob.get("data_vencimento"))
    valor = formatar_valor(cob.get("valor_total_a_pagar"))
    return {
        "resposta": f"""*Dados da fatura:*
📅 Vencimento: {vencimento}
💰 Valor: {valor}

Como deseja receber?

1 - Pix (copia e cola)
2 - Código de barras
3 - Boleto em PDF"""
    }


def executar_fluxo_boleto_todos_planos(ctx: dict, atendimento_id: int = None) -> dict:
    """Função auxiliar para listar boletos de todos os planos do cliente (consulta por id_cliente)"""
    
    def formatar_data_local(data_str: str) -> str:
        if not data_str:
            return "Data não informada"
        try:
            from datetime import datetime
            if isinstance(data_str, str):
                data = datetime.strptime(data_str, '%Y-%m-%d')
                return data.strftime('%d/%m/%Y')
            return str(data_str)
        except:
            return str(data_str)
    
    def formatar_valor_local(valor) -> str:
        if valor is None:
            return "R$ 0,00"
        try:
            valor_float = float(valor)
            return f"R$ {valor_float:,.2f}".replace(",", "v").replace(".", ",").replace("v", ".")
        except:
            return str(valor)
    
    id_cliente = ctx.get("id_cliente")
    if not id_cliente:
        return {"resposta": "Erro: cliente não identificado."}
    
    log(f"[FLUXO_BOLETO_TODOS] Buscando cobranças de todos os planos para ID_CLIENTE={id_cliente}", "INFO")
    
    resultado = chamar_api_radiusnet("cobrancas_cliente_nao_pagas", id_cliente=id_cliente)
    if not resultado.get("success"):
        log(f"[FLUXO_BOLETO_TODOS] Erro na consulta: {resultado.get('error')}", "ERROR")
        return {"resposta": f"Não foi possível buscar as cobranças: {resultado.get('error', 'Erro desconhecido')}"}
    
    cobrancas = resultado.get("data", [])
    log(f"[FLUXO_BOLETO_TODOS] Encontradas {len(cobrancas)} cobranças não pagas em todos os planos", "INFO")
    
    if not cobrancas:
        return {"resposta": "✅ Não encontrei cobranças pendentes em nenhum dos seus planos. Sua conta está em dia!"}
    
    resposta = "📄 *Cobranças pendentes em todos os seus planos:*\n\n"
    for i, cob in enumerate(cobrancas, 1):
        plano_nome = cob.get("plano") or "Plano não identificado"
        periodo = cob.get("periodo_prestacao", "?")
        vencimento = formatar_data_local(cob.get("data_vencimento"))
        valor = formatar_valor_local(cob.get("valor_total_a_pagar"))
        tipo = "📄 AVULSA" if cob.get("itensAvulsa") else "📄 FATURA"
        resposta += f"{i} - *{plano_nome}* - {tipo}\n"
        resposta += f"   Período: {periodo} - Venc: {vencimento} - {valor}\n\n"
    
    resposta += "Digite o número da cobrança que deseja pagar."
    ctx["lista_cobrancas_todos_planos"] = cobrancas
    ctx["aguardando_escolha_cobranca_todos"] = True
    return {"resposta": resposta}