diff --git a/01 - Estrutura de dados/desafio.py b/01 - Estrutura de dados/desafio.py index 2ce9ef270..ee3acb6c0 100644 --- a/01 - Estrutura de dados/desafio.py +++ b/01 - Estrutura de dados/desafio.py @@ -1,160 +1,342 @@ -import textwrap +from abc import ABC, abstractmethod +from datetime import datetime + + +class Cliente: + def __init__(self, endereco): + self.endereco = endereco + self.contas = [] + + def realizar_transacao(self, conta, transacao): + transacao.registrar(conta) + + def adicionar_conta(self, conta): + self.contas.append(conta) + + +class PessoaFisica(Cliente): + def __init__(self, nome, data_nascimento, cpf, endereco): + super().__init__(endereco) + self.nome = nome + self.data_nascimento = data_nascimento + self.cpf = cpf + + +class Conta: + def __init__(self, numero, cliente): + self._saldo = 0 + self._numero = numero + self._agencia = "0001" + self._cliente = cliente + self._historico = Historico() + + @classmethod + def nova_conta(cls, cliente, numero): + return cls(numero, cliente) + + @property + def saldo(self): + return self._saldo + + @property + def numero(self): + return self._numero + + @property + def agencia(self): + return self._agencia + + @property + def cliente(self): + return self._cliente + + @property + def historico(self): + return self._historico + + def sacar(self, valor): + if valor <= 0: + print("Operação falhou! Valor inválido.") + return False + if valor > self._saldo: + print("Operação falhou! Saldo insuficiente.") + return False -def menu(): - menu = """\n - ================ MENU ================ - [d]\tDepositar - [s]\tSacar - [e]\tExtrato - [nc]\tNova conta - [lc]\tListar contas - [nu]\tNovo usuário - [q]\tSair - => """ - return input(textwrap.dedent(menu)) - - -def depositar(saldo, valor, extrato, /): - if valor > 0: - saldo += valor - extrato += f"Depósito:\tR$ {valor:.2f}\n" - print("\n=== Depósito realizado com sucesso! ===") - else: - print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + self._saldo -= valor + print("Saque realizado com sucesso!") + return True - return saldo, extrato + def depositar(self, valor): + if valor <= 0: + print("Operação falhou! Valor inválido.") + return False + self._saldo += valor + print("Depósito realizado com sucesso!") + return True -def sacar(*, saldo, valor, extrato, limite, numero_saques, limite_saques): - excedeu_saldo = valor > saldo - excedeu_limite = valor > limite - excedeu_saques = numero_saques >= limite_saques - if excedeu_saldo: - print("\n@@@ Operação falhou! Você não tem saldo suficiente. @@@") +class ContaCorrente(Conta): + def __init__(self, numero, cliente, limite=500, limite_saques=3): + super().__init__(numero, cliente) + self.limite = limite + self.limite_saques = limite_saques - elif excedeu_limite: - print("\n@@@ Operação falhou! O valor do saque excede o limite. @@@") + def sacar(self, valor): + numero_saques = len( + [ + transacao + for transacao in self.historico.transacoes + if transacao["tipo"] == "Saque" + ] + ) - elif excedeu_saques: - print("\n@@@ Operação falhou! Número máximo de saques excedido. @@@") + if valor > self.limite: + print("Operação falhou! Valor excede o limite.") + return False - elif valor > 0: - saldo -= valor - extrato += f"Saque:\t\tR$ {valor:.2f}\n" - numero_saques += 1 - print("\n=== Saque realizado com sucesso! ===") + if numero_saques >= self.limite_saques: + print("Operação falhou! Número máximo de saques excedido.") + return False + + return super().sacar(valor) - else: - print("\n@@@ Operação falhou! O valor informado é inválido. @@@") + def __str__(self): + return f""" +Agência:\t{self.agencia} +Conta:\t\t{self.numero} +Titular:\t{self.cliente.nome} +Saldo:\t\tR$ {self.saldo:.2f} +""" + + +class Historico: + def __init__(self): + self.transacoes = [] + + def adicionar_transacao(self, transacao): + self.transacoes.append( + { + "tipo": transacao.__class__.__name__, + "valor": transacao.valor, + "data": datetime.now().strftime("%d/%m/%Y %H:%M:%S"), + } + ) + + +class Transacao(ABC): + @property + @abstractmethod + def valor(self): + pass + + @abstractmethod + def registrar(self, conta): + pass + + +class Saque(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso = conta.sacar(self.valor) + + if sucesso: + conta.historico.adicionar_transacao(self) + + +class Deposito(Transacao): + def __init__(self, valor): + self._valor = valor + + @property + def valor(self): + return self._valor + + def registrar(self, conta): + sucesso = conta.depositar(self.valor) + + if sucesso: + conta.historico.adicionar_transacao(self) + + +def menu(): + return """ +================ MENU ================ + +[nu] Novo usuário +[nc] Nova conta +[lc] Listar contas + +[d] Depositar +[s] Sacar +[e] Extrato + +[q] Sair + +=> """ + + +def filtrar_cliente(cpf, clientes): + clientes_filtrados = [cliente for cliente in clientes if cliente.cpf == cpf] + return clientes_filtrados[0] if clientes_filtrados else None - return saldo, extrato +def recuperar_conta_cliente(cliente): + if not cliente.contas: + print("Cliente não possui conta.") + return None -def exibir_extrato(saldo, /, *, extrato): - print("\n================ EXTRATO ================") - print("Não foram realizadas movimentações." if not extrato else extrato) - print(f"\nSaldo:\t\tR$ {saldo:.2f}") - print("==========================================") + return cliente.contas[0] -def criar_usuario(usuarios): - cpf = input("Informe o CPF (somente número): ") - usuario = filtrar_usuario(cpf, usuarios) +def criar_cliente(clientes): + cpf = input("Informe o CPF: ").strip() - if usuario: - print("\n@@@ Já existe usuário com esse CPF! @@@") + if not cpf: + print("CPF inválido.") return - nome = input("Informe o nome completo: ") - data_nascimento = input("Informe a data de nascimento (dd-mm-aaaa): ") - endereco = input("Informe o endereço (logradouro, nro - bairro - cidade/sigla estado): ") + if filtrar_cliente(cpf, clientes): + print("Já existe cliente com esse CPF.") + return + + nome = input("Nome completo: ") + data_nascimento = input("Data de nascimento (dd/mm/aaaa): ") + endereco = input("Endereço: ") - usuarios.append({"nome": nome, "data_nascimento": data_nascimento, "cpf": cpf, "endereco": endereco}) + cliente = PessoaFisica( + nome=nome, + data_nascimento=data_nascimento, + cpf=cpf, + endereco=endereco, + ) - print("=== Usuário criado com sucesso! ===") + clientes.append(cliente) + print("Cliente criado com sucesso!") -def filtrar_usuario(cpf, usuarios): - usuarios_filtrados = [usuario for usuario in usuarios if usuario["cpf"] == cpf] - return usuarios_filtrados[0] if usuarios_filtrados else None +def criar_conta(numero_conta, clientes, contas): + cpf = input("Informe o CPF do cliente: ") -def criar_conta(agencia, numero_conta, usuarios): - cpf = input("Informe o CPF do usuário: ") - usuario = filtrar_usuario(cpf, usuarios) + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("Cliente não encontrado.") + return - if usuario: - print("\n=== Conta criada com sucesso! ===") - return {"agencia": agencia, "numero_conta": numero_conta, "usuario": usuario} + conta = ContaCorrente.nova_conta( + cliente=cliente, + numero=numero_conta, + ) - print("\n@@@ Usuário não encontrado, fluxo de criação de conta encerrado! @@@") + cliente.adicionar_conta(conta) + contas.append(conta) + + print("Conta criada com sucesso!") def listar_contas(contas): + if not contas: + print("Nenhuma conta cadastrada.") + return + for conta in contas: - linha = f"""\ - Agência:\t{conta['agencia']} - C/C:\t\t{conta['numero_conta']} - Titular:\t{conta['usuario']['nome']} - """ - print("=" * 100) - print(textwrap.dedent(linha)) - - -def main(): - LIMITE_SAQUES = 3 - AGENCIA = "0001" - - saldo = 0 - limite = 500 - extrato = "" - numero_saques = 0 - usuarios = [] - contas = [] - - while True: - opcao = menu() - - if opcao == "d": - valor = float(input("Informe o valor do depósito: ")) - - saldo, extrato = depositar(saldo, valor, extrato) - - elif opcao == "s": - valor = float(input("Informe o valor do saque: ")) - - saldo, extrato = sacar( - saldo=saldo, - valor=valor, - extrato=extrato, - limite=limite, - numero_saques=numero_saques, - limite_saques=LIMITE_SAQUES, + print("=" * 40) + print(conta) + + +def exibir_extrato(cliente): + conta = recuperar_conta_cliente(cliente) + + if not conta: + return + + print("\n============== EXTRATO ==============") + + if not conta.historico.transacoes: + print("Não foram realizadas movimentações.") + + else: + for transacao in conta.historico.transacoes: + print( + f"{transacao['data']} - " + f"{transacao['tipo']}: " + f"R$ {transacao['valor']:.2f}" ) - elif opcao == "e": - exibir_extrato(saldo, extrato=extrato) + print(f"\nSaldo atual: R$ {conta.saldo:.2f}") + print("====================================") + + +clientes = [] +contas = [] + +while True: + opcao = input(menu()).lower() + + if opcao == "nu": + criar_cliente(clientes) + + elif opcao == "nc": + numero_conta = len(contas) + 1 + criar_conta(numero_conta, clientes, contas) + + elif opcao == "lc": + listar_contas(contas) - elif opcao == "nu": - criar_usuario(usuarios) + elif opcao == "d": + cpf = input("CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) - elif opcao == "nc": - numero_conta = len(contas) + 1 - conta = criar_conta(AGENCIA, numero_conta, usuarios) + if not cliente: + print("Cliente não encontrado.") + continue - if conta: - contas.append(conta) + conta = recuperar_conta_cliente(cliente) - elif opcao == "lc": - listar_contas(contas) + try: + valor = float(input("Valor do depósito: ")) + cliente.realizar_transacao(conta, Deposito(valor)) + except ValueError: + print("Digite um valor numérico válido.") - elif opcao == "q": - break + elif opcao == "s": + cpf = input("CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) - else: - print("Operação inválida, por favor selecione novamente a operação desejada.") + if not cliente: + print("Cliente não encontrado.") + continue + conta = recuperar_conta_cliente(cliente) -main() + try: + valor = float(input("Valor do saque: ")) + cliente.realizar_transacao(conta, Saque(valor)) + except ValueError: + print("Digite um valor numérico válido.") + + elif opcao == "e": + cpf = input("CPF do cliente: ") + cliente = filtrar_cliente(cpf, clientes) + + if not cliente: + print("Cliente não encontrado.") + continue + + exibir_extrato(cliente) + + elif opcao == "q": + print("Encerrando sistema...") + break + + else: + print("Operação inválida.") diff --git a/README.md b/README.md index 0831f58c9..7723673f8 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ # Trilha Python DIO +## Sistema Bancário Otimizado com Python (POO) + +Este projeto consiste na reformulação completa de um sistema bancário básico, migrando uma estrutura sequencial/funcional para o paradigma de **Programação Orientada a Objetos (POO)** em Python. O desafio faz parte do Bootcamp da Digital Innovation One (DIO). + +## Tecnologias e Conceitos Aplicados + +* **Python 3**: Linguagem base utilizada. +* **Classes e Objetos**: Estruturação de entidades do mundo real como `Cliente`, `PessoaFisica`, `Conta` e `ContaCorrente`. +* **Herança e Polimorfismo**: Reaproveitamento e extensão da classe base `Conta` para criar uma `ContaCorrente` com regras específicas de limite. +* **Classes Abstratas (ABC)**: Implementação da classe `Transacao` utilizando o decorador `@abstractmethod`. +* **Encapsulamento**: Proteção de atributos críticos (como saldo e histórico) utilizando propriedades (`@property`). +* **Tratamento de Exceções**: Uso de blocos `try/except` com `ValueError` para evitar travamentos caso o usuário digite letras no lugar de números nos campos de valores. + +## Funcionalidades Cadastrada + +* **Cadastro de Clientes**: Criação de usuários do tipo Pessoa Física com validação para impedir CPFs duplicados. +* **Vínculo de Contas**: Abertura automática de contas correntes vinculadas ao CPF do cliente.