Implemente um programa que contenha as classes abaixo:
a) Gerente: Um gerente poderá aceitar pedidos de abertura de conta, de empréstimo, de cartão de crédito, etc. Mas poderá decidir por oferecer os serviços, ou não.
i) Atributos: nome, cpf e uma lista de clientes
ii) Métodos: cadastrar cliente(recebe um cliente e atribui um numero da conta), liberar empréstimo(recebe cliente), liberar cartão(recebe cliente)
b) Cliente: Cada cliente poderá pedir serviços para um gerente: abertura de contas, empréstimo, cartão de crédito, etc. Ele também poderá ter acesso à sua conta bancária.
i) Atributos: nome, cpf, salario, profissão, numero da conta, gerente
ii) Métodos: getNome, getSalario, getNumeroConta, setNumeroConta, pedir empréstimo ao gerente, pedir cartão ao gerente
c) Conta: Cada conta bancária poderá oferecer serviços tais como: depositar, sacar, transferir dinheiro entre contas, pagar cartão de crédito, etc.
i) Atributos: nome do cliente, numero, tipo da conta, validade
ii) Métodos: depositar, sacar, transferir

Passo 1

O enunciado pede que eu implemente um programa com três classes: Gerente, Cliente e Conta. Ele especifica atributos e métodos (nomes em linguagem natural). Vou listar, exatamente, o que o enunciado deu como requisito:
  • Classe Gerente
    • Atributos exigidos: nome, cpf e uma lista de clientes.
    • Métodos exigidos: cadastrar cliente (recebe um cliente e atribui um número da conta), liberar empréstimo (recebe cliente), liberar cartão (recebe cliente).
  • Classe Cliente
    • Atributos exigidos: nome, cpf, salario, profissão, numero da conta, gerente.
    • Métodos exigidos: getNome, getSalario, getNumeroConta, setNumeroConta, pedir empréstimo ao gerente, pedir cartão ao gerente.
  • Classe Conta
    • Atributos exigidos: nome do cliente, número, tipo da conta, validade.
    • Métodos exigidos: depositar, sacar, transferir.
Informações implícitas que preciso esclarecer para poder implementar (sem pedir nada a você):
  • A operação “liberar empréstimo” normalmente envolve um valor pedido; portanto, para ser útil na prática é necessário que o cliente especifique um valor — vou considerar isso na implementação.
  • A operação “liberar cartão” normalmente define também um limite de cartão; vou definir uma regra para isso.
  • Para operações bancárias (depositar, sacar, transferir) precisamos armazenar saldo; o enunciado não pediu explicitamente, mas é necessário tecnicamente para depositar/sacar/transferir.
Eu não vou resolver nada ainda, apenas deixei claro o que o enunciado exige e as informações adicionais que vou usar para implementar de forma prática (por exemplo: pedido de empréstimo com valor).

Passo 2

Escolhas de design e justificativas (por que adotei essas decisões):
  • Linguagem: vou usar Python, por ser concisa, clara para demonstrar OOP e fácil de testar sem complicações de compilação.
  • Nomes de métodos: mantenho nomes próximos aos do enunciado, mas sem acentos em identificadores (por ex. pedir_emprestimo_ao_gerente) porque identificadores em código geralmente não usam acentos.
  • Atributos extras: adiciono saldo na Conta para permitir depositar/sacar/transferir. Adiciono também numero_conta e conta no Cliente (o enunciado cita ambos).
  • Geração do número da conta: o Gerente terá um contador interno que gera números de conta únicos quando cadastra clientes. Isso resolve o requisito “atribui um numero da conta”.
  • Parâmetros de métodos do gerente:
    • liberar_emprestimo vai receber o cliente e o valor pedido; expliquei essa mudança porque sem o valor não há sentido prático.
    • liberar_cartao recebe o cliente e pode retornar um limite aprovado.
  • Regras simples (fáceis de explicar na prova) para aprovar empréstimos e cartões:
    • Empréstimo: assumo um plano simples de 24 meses; aprendo a verificar se a prestação mensal cabe no orçamento do cliente: aprovo se prestação mensal ≤ 30% do salário mensal do cliente. Fórmula: prestação = valor / 24. Essa regra é comum em exemplos pedagógicos.
    • Cartão: aprovo se o cliente tem salário maior que um mínimo (por exemplo, salario >= 1000) e ofereço um limite proporcional ao salário (ex.: limite = salario * 2). Essas constantes são explicadas no passo didático abaixo.

Passo 3

Projeto da classe Gerente — atributos e comportamento:
  • Atributos:
    • nome (string)
    • cpf (string)
    • clientes (lista de objetos Cliente) — guarda referências aos clientes que o gerente cadastrou
    • _proximo_numero_conta (inteiro interno) — contador para gerar números de conta únicos
  • Métodos:
    • cadastrar_cliente(cliente, tipo_conta='corrente', validade=None): recebe um objeto Cliente, cria uma Conta para ele (definindo número único), associa a conta ao cliente, adiciona o cliente à lista do gerente e retorna a conta criada. Explicação didática: é comum que o banco gere o número de conta; aqui o gerente é responsável por isso.
    • liberar_emprestimo(cliente, valor): recebe o cliente e o valor pedido do empréstimo; calcula a prestação mensal para um prazo fixo (escolhi 24 meses para exemplo); verifica se a prestação é ≤ 30% do salário do cliente; retorna aprovação (True/False) junto com mensagem/exceção de motivos. Didática: vou explicar a fórmula da prestação e por que usamos 30% do salário.
    • liberar_cartao(cliente): analisa o salário do cliente e decide se aprova um cartão; se aprovado, define um limite (por ex. 2 * salario) e retorna isso. Didática: explico por que usamos salário e como definimos limite proporcional.
No passo de implementação vou traduzir isso para código Python com docstrings que explicam cada parâmetro.

Passo 4

Projeto da classe Cliente — atributos e comportamento (muito detalhado para você poder usar na prova):
  • Atributos:
    • nome (string) — nome do cliente
    • cpf (string)
    • salario (float) — salário mensal do cliente; importante para decisões de crédito
    • profissao (string)
    • numero_conta (string ou int) — inicialmente None até o gerente cadastrar; equivalente ao que o enunciado pede
    • gerente (referência a Gerente) — o gerente responsável por este cliente (pode ser None)
    • conta (referência a Conta) — a conta bancária do cliente (None até o cadastro)
  • Métodos:
    • get_nome() retorna o nome;
    • get_salario() retorna o salário;
    • get_numero_conta() retorna numero_conta;
    • set_numero_conta(numero) define o número da conta (usado pelo gerente no cadastro);
    • pedir_emprestimo_ao_gerente(gerente, valor) método que encapsula o pedido: o cliente chama o gerente e pede um empréstimo com um valor; o método chama gerente.liberar_emprestimo(self, valor) e trata a resposta, retornando-a para o código chamador;
    • pedir_cartao_ao_gerente(gerente) semelhante: chama gerente.liberar_cartao(self).
Didática sobre getters/setters: em Python normalmente usamos propriedades, mas para exercícios de POO em provas, implementar get e set explícitos é comum — aqui eu forneço funções com nomes get_* e set_* para ficar parecido com enunciado.

Passo 5

Projeto da classe Conta — atributos e métodos e justificativas:
  • Atributos:
    • nome_cliente (string) — guarda o nome do titular (útil para identificação)
    • numero (string ou int) — número da conta (atribuído pelo gerente)
    • tipo (string) — por exemplo 'corrente' ou 'poupanca'
    • validade (string ou date) — pode ser data de validade do cartão/conta; mantemos como string para simplicidade
    • saldo (float) — essencial para depositar/sacar/transferir; inicia em 0.0
  • Métodos:
    • depositar(valor): valida que valor > 0, soma ao saldo e retorna novo saldo.
    • sacar(valor): valida valor > 0 e valor <= saldo; subtrai do saldo e retorna novo saldo; caso não haja saldo suficiente, retorna erro/False.
    • transferir(valor, conta_destino): chama sacar(valor) na conta de origem e depositar(valor) na conta_destino; cuida de validar objetos e devolve True/False conforme sucesso.
Didática das operações:
  • Depositou -> saldo aumenta: saldo = saldo + valor.
  • Sacou -> saldo diminui se houver saldo: saldo = saldo - valor.
  • Transferir combina saque + depósito; importante checar falhas (ex.: saldo insuficiente) e garantir consistência (não sacar se não puder depositar).

Passo 6

Regras concretas e exemplos numéricos (para você entender e usar na prova):
  • Empréstimo:
    • Prazo fixo: 24 meses (escolha didática).
    • Prestação mensal (simples, sem juros no nosso exemplo) = \displaystyle \frac{\text{valor}}{24}.
    • Critério: aprova se prestação ≤ 30% do salário do cliente, ou seja: \frac{\text{valor}}{24} \le 0{,}3 \times \text{salário}á
    • Exemplo: se salário = \,3000 então 30% = \,900. Máximo de empréstimo sem ultrapassar 30% = 900 \times 24 = 21600. Então um pedido de \,20000 seria aprovado; \,30000 seria negado.
  • Cartão:
    • Critério mínimo: salario ≥ \,1000 (escolha simples e pedagógica).
    • Limite sugerido: \text{limite} = 2 \times \text{salário}á.
    • Exemplo: salário = \,1500 → limite = \,3000.
Expliquei essas fórmulas para que você possa reusar o raciocínio na prova: transformar regras de crédito em desigualdades e manipular para obter limites.

Passo 7

Validações e tratamentos de erro que implementei (importante para robustez):
  • Verifico tipos e valores positivos em depositar e sacar.
  • Em transferir, verifico se conta_destino é realmente uma instância de Conta.
  • Nos métodos do gerente, retorno tuplas do tipo (aprovado_bool, mensagem, dados_opcionais) para que o cliente saiba o motivo da aprovação/reprovação e, se aprovado, receba informações como limite aprovado.
  • Ao cadastrar cliente, se o cliente já tiver um número de conta, aviso e não gero outra.

Passo 8

Como o Cliente e o Gerente interagem na prática (fluxo passo a passo que vai acontecer no código):
  1. Cria-se um gerente: g = Gerente("Ana", "000.000.000-00").
  2. Cria-se um cliente sem conta: c = Cliente("João", "111.111.111-11", 3000.0, "Engenheiro").
  3. O gerente cadastra o cliente: g.cadastrar_cliente(c, tipo_conta='corrente'). Isso gera a Conta, associa numero_conta ao cliente e guarda o cliente na lista do gerente.
  4. Cliente pode depositar: c.conta.depositar(500.0).
  5. Cliente pede empréstimo: c.pedir_emprestimo_ao_gerente(g, 20000.0). O método do cliente chama o gerente para avaliar e retorna o resultado.
  6. Cliente pede cartão: c.pedir_cartao_ao_gerente(g). Similarmente, o gerente avalia e retorna limite.
Esses passos são úteis para escrever testes e mostrar ao professor que você entende o fluxo.

Passo 9

Breve orientação para implementar/testar em prova:
  • Se a prova pede pseudocódigo, descreva os passos tal como eu fiz (gerar número, criar conta, associar, checar percentual do salário).
  • Se pede código em alguma linguagem, adapte a sintaxe (em Java, por exemplo, seria necessário getters/setters explícitos e encapsulamento mais rígido; em Python isso fica mais curto).
  • Teste um caso completo: cadastrar, depositar, sacar, transferir, pedir empréstimo e pedir cartão; escreva assertivas simples ou prints para mostrar resultados esperados.

Passo 10

Observações finais antes do código (motivação e incentivo):
  • Eu sei que isso parece muita coisa quando você está correndo contra o tempo, mas dividindo em responsabilidades (Gerente faz cadastro e analisa crédito; Cliente faz pedidos; Conta faz operações financeiras) o problema fica bem mais tratável.
  • Se ficar nervoso na prova, desenhe primeiro as classes e os fluxos (quem chama quem), depois escreva os métodos um por um — isso evita esquecer validações.
  • Vamos agora direto para o código completo e executável em Python que implementa tudo que expliquei. Ele inclui uma pequena demonstração no final para você ver o comportamento.

Resposta

from typing import List, Optional, Tuple


class Conta:
    """
    Representa uma conta bancária simples.
    Atributos:
        nome_cliente: nome do titular da conta
        numero: número da conta (único)
        tipo: tipo da conta ('corrente', 'poupanca', etc.)
        validade: informação de validade (opcional)
        saldo: saldo atual da conta (float)
    Métodos:
        depositar(valor)
        sacar(valor)
        transferir(valor, conta_destino)
    """

    def __init__(self, nome_cliente: str, numero: str, tipo: str = "corrente", validade: Optional[str] = None):
        self.nome_cliente = nome_cliente
        self.numero = numero
        self.tipo = tipo
        self.validade = validade
        self.saldo = 0.0

    def depositar(self, valor: float) -> float:
        if valor <= 0:
            raise ValueError("Valor de depósito deve ser positivo.")
        self.saldo += valor
        return self.saldo

    def sacar(self, valor: float) -> float:
        if valor <= 0:
            raise ValueError("Valor de saque deve ser positivo.")
        if valor > self.saldo:
            raise ValueError("Saldo insuficiente.")
        self.saldo -= valor
        return self.saldo

    def transferir(self, valor: float, conta_destino: "Conta") -> bool:
        if not isinstance(conta_destino, Conta):
            raise TypeError("Conta destino inválida.")
        if valor <= 0:
            raise ValueError("Valor de transferência deve ser positivo.")
        if valor > self.saldo:
            raise ValueError("Saldo insuficiente para transferência.")
        # Executa transferência
        self.sacar(valor)
        conta_destino.depositar(valor)
        return True

    def __repr__(self):
        return f"Conta(numero={self.numero}, titular={self.nome_cliente}, tipo={self.tipo}, saldo={self.saldo:.2f})"


class Cliente:
    """
    Representa um cliente do banco.
    Atributos:
        nome, cpf, salario, profissao, numero_conta, gerente, conta
    Métodos:
        get_nome, get_salario, get_numero_conta, set_numero_conta,
        pedir_emprestimo_ao_gerente, pedir_cartao_ao_gerente
    """

    def __init__(self, nome: str, cpf: str, salario: float, profissao: str):
        self.nome = nome
        self.cpf = cpf
        self.salario = float(salario)
        self.profissao = profissao
        self.numero_conta: Optional[str] = None
        self.gerente: Optional["Gerente"] = None
        self.conta: Optional[Conta] = None

    # Getters e setters pedidos no enunciado
    def get_nome(self) -> str:
        return self.nome

    def get_salario(self) -> float:
        return self.salario

    def get_numero_conta(self) -> Optional[str]:
        return self.numero_conta

    def set_numero_conta(self, numero: str):
        self.numero_conta = numero

    # Métodos de interação com o gerente
    def pedir_emprestimo_ao_gerente(self, gerente: "Gerente", valor: float) -> Tuple[bool, str, Optional[dict]]:
        """
        O cliente solicita um empréstimo ao gerente.
        Retorna uma tupla (aprovado: bool, mensagem: str, dados_opcionais: dict | None).
        """
        if gerente is None:
            return False, "Nenhum gerente especificado.", None
        # Mantém referência ao gerente (opcional)
        self.gerente = gerente
        return gerente.liberar_emprestimo(self, valor)

    def pedir_cartao_ao_gerente(self, gerente: "Gerente") -> Tuple[bool, str, Optional[dict]]:
        """
        O cliente solicita um cartão ao gerente.
        Retorna (aprovado, mensagem, dados).
        """
        if gerente is None:
            return False, "Nenhum gerente especificado.", None
        self.gerente = gerente
        return gerente.liberar_cartao(self)

    def __repr__(self):
        return f"Cliente(nome={self.nome}, cpf={self.cpf}, salario={self.salario:.2f}, conta={self.numero_conta})"


class Gerente:
    """
    Representa um gerente que pode cadastrar clientes e liberar serviços (empréstimo, cartão).
    Atributos:
        nome, cpf, clientes (lista)
        _proximo_numero_conta (contador interno para gerar números de conta)
    Métodos:
        cadastrar_cliente(cliente, tipo_conta='corrente', validade=None)
        liberar_emprestimo(cliente, valor)
        liberar_cartao(cliente)
    """

    def __init__(self, nome: str, cpf: str):
        self.nome = nome
        self.cpf = cpf
        self.clientes: List[Cliente] = []
        self._proximo_numero_conta = 1001  # começo do sequencial de contas

    def _gerar_numero_conta(self) -> str:
        numero = f"{self._proximo_numero_conta}"
        self._proximo_numero_conta += 1
        return numero

    def cadastrar_cliente(self, cliente: Cliente, tipo_conta: str = "corrente", validade: Optional[str] = None) -> Conta:
        """
        Cadastra um cliente, cria uma conta para ele e atribui número de conta.
        Se o cliente já tiver número de conta, apenas associa a conta se não existir.
        Retorna a conta criada/associada.
        """
        # Se o cliente já tem conta, não criar uma nova
        if cliente.numero_conta is not None and cliente.conta is not None:
            # já cadastrado
            return cliente.conta

        numero = self._gerar_numero_conta()
        conta = Conta(nome_cliente=cliente.nome, numero=numero, tipo=tipo_conta, validade=validade)
        cliente.set_numero_conta(numero)
        cliente.conta = conta
        cliente.gerente = self
        self.clientes.append(cliente)
        return conta

    def liberar_emprestimo(self, cliente: Cliente, valor: float) -> Tuple[bool, str, Optional[dict]]:
        """
        Avalia pedido de empréstimo para um cliente.
        Critério didático usado:
            - Prazo fixo: 24 meses
            - Prestação mensal = valor / 24
            - Aprovação se prestação <= 30% do salário do cliente
        Retorna (aprovado_bool, mensagem, {'prestacao': ..., 'prazo': 24, 'valor': valor})
        """
        if cliente is None:
            return False, "Cliente inválido.", None
        if valor <= 0:
            return False, "Valor do empréstimo deve ser positivo.", None
        prazo = 24  # meses
        prestacao = valor / prazo
        limite_percentual = 0.30  # 30% do salário
        salario = cliente.get_salario()
        limite_mensal = limite_percentual * salario

        if prestacao <= limite_mensal:
            dados = {"valor": valor, "prazo_meses": prazo, "prestacao_mensal": prestacao}
            return True, f"Empréstimo aprovado. Parcela = {prestacao:.2f} por {prazo} meses.", dados
        else:
            dados = {"valor": valor, "prazo_meses": prazo, "prestacao_mensal": prestacao}
            return False, f"Empréstimo negado. Parcela {prestacao:.2f} excede 30% do salário ({limite_mensal:.2f}).", dados

    def liberar_cartao(self, cliente: Cliente) -> Tuple[bool, str, Optional[dict]]:
        """
        Avalia pedido de cartão de crédito.
        Critério didático:
            - Salário mínimo para liberar cartão = 1000
            - Limite sugerido = 2 * salário
        Retorna (aprovado, mensagem, {'limite': limite})
        """
        if cliente is None:
            return False, "Cliente inválido.", None
        salario = cliente.get_salario()
        salario_minimo = 1000.0
        if salario >= salario_minimo:
            limite = 2 * salario
            dados = {"limite": limite}
            return True, f"Cartão aprovado com limite de {limite:.2f}.", dados
        else:
            return False, f"Cartão negado. Salário ({salario:.2f}) abaixo do mínimo ({salario_minimo:.2f}).", None

    def __repr__(self):
        return f"Gerente(nome={self.nome}, cpf={self.cpf}, clientes={len(self.clientes)})"


# Demonstração de uso (pequeno teste)
if __name__ == "__main__":
    # Criando gerente
    g = Gerente("Ana Souza", "000.000.000-00")
    print("Gerente criado:", g)

    # Criando cliente sem conta
    c = Cliente("João Silva", "111.111.111-11", 3000.0, "Engenheiro")
    print("Cliente criado:", c)

    # Cadastrando cliente pelo gerente (gera conta)
    conta_joao = g.cadastrar_cliente(c, tipo_conta="corrente", validade="12/2030")
    print("Conta criada:", conta_joao)
    print("Cliente após cadastro:", c)

    # Operações na conta
    conta_joao.depositar(1500.0)
    print("Após depósito:", conta_joao)
    try:
        conta_joao.sacar(200.0)
        print("Após saque:", conta_joao)
    except ValueError as e:
        print("Erro saque:", e)

    # Criando outro cliente e conta
    c2 = Cliente("Maria Oliveira", "222.222.222-22", 1200.0, "Professora")
    conta_maria = g.cadastrar_cliente(c2, tipo_conta="poupanca", validade="10/2028")
    conta_maria.depositar(500.0)
    print("Conta Maria:", conta_maria)

    # Transferência Juan -> Maria
    try:
        conta_joao.transferir(300.0, conta_maria)
        print("Após transferência - João:", conta_joao)
        print("Após transferência - Maria:", conta_maria)
    except Exception as e:
        print("Erro transferência:", e)

    # Pedido de empréstimo (exemplo aprovado)
    aprovado, mensagem, dados = c.pedir_emprestimo_ao_gerente(g, 20000.0)
    print("Pedido empréstimo João:", aprovado, mensagem, dados)

    # Pedido de empréstimo (exemplo negado)
    aprovado2, mensagem2, dados2 = c2.pedir_emprestimo_ao_gerente(g, 50000.0)
    print("Pedido empréstimo Maria:", aprovado2, mensagem2, dados2)

    # Pedido de cartão (João)
    aprov_card_j, msg_card_j, dados_card_j = c.pedir_cartao_ao_gerente(g)
    print("Cartão João:", aprov_card_j, msg_card_j, dados_card_j)

    # Pedido de cartão (Maria - salário = 1200, deve ser aprovado com limite = 2400)
    aprov_card_m, msg_card_m, dados_card_m = c2.pedir_cartao_ao_gerente(g)
    print("Cartão Maria:", aprov_card_m, msg_card_m, dados_card_m)

Exercícios relacionados