MB Academy

Implementando o Rastreador de Gastos

Vamos escrever o código do rastreador de gastos, implementando os três arquivos do projeto com TypeScript.

Com o planejamento feito, é hora de implementar. Vamos construir os três arquivos do projeto, do mais simples ao mais complexo.

1. Os tipos: src/tipos.ts

Começamos pelos tipos — a fundação de tudo. Esse arquivo não tem lógica, apenas definições:

// src/tipos.ts

export type Categoria =
  | "alimentação"
  | "transporte"
  | "moradia"
  | "lazer"
  | "saúde"
  | "outros";

export interface Gasto {
  id: number;
  descricao: string;
  valor: number;
  categoria: Categoria;
  data: string;
}

Exportamos tudo para que os outros arquivos possam importar. O union type Categoria garante que nenhum arquivo do projeto consiga usar uma categoria inválida — o TypeScript vai acusar erro em tempo de compilação.

2. A classe: src/rastreador.ts

Aqui está toda a lógica do projeto. Vamos implementar método por método.

Estrutura inicial

// src/rastreador.ts

import { Gasto, Categoria } from "./tipos";

export class RastreadorDeGastos {
  private gastos: Gasto[] = [];
  private proximoId: number = 1;
}

adicionarGasto

Valida o valor recebido e cria um novo objeto Gasto. Usamos throw para rejeitar valores inválidos:

adicionarGasto(descricao: string, valor: number, categoria: Categoria): void {
  if (valor <= 0) {
    throw new Error("O valor do gasto deve ser maior que zero.");
  }

  const novoGasto: Gasto = {
    id: this.proximoId++,
    descricao,
    valor,
    categoria,
    data: new Date().toLocaleDateString("pt-BR"),
  };

  this.gastos.push(novoGasto);
  console.log(`✓ "${descricao}" — R$ ${valor.toFixed(2)} adicionado.`);
}

listarGastos

Usa console.table para exibir os gastos em uma tabela formatada automaticamente no terminal:

listarGastos(): void {
  if (this.gastos.length === 0) {
    console.log("Nenhum gasto registrado ainda.");
    return;
  }

  const tabela = this.gastos.map((g) => ({
    ID: g.id,
    Descrição: g.descricao,
    "Valor (R$)": g.valor.toFixed(2),
    Categoria: g.categoria,
    Data: g.data,
  }));

  console.log("\n=== Todos os Gastos ===");
  console.table(tabela);
}

resumoPorCategoria

Agrupa os gastos por categoria usando um objeto como acumulador, depois exibe em tabela:

resumoPorCategoria(): void {
  if (this.gastos.length === 0) {
    console.log("Nenhum gasto registrado ainda.");
    return;
  }

  const totais: Record<string, number> = {};

  for (const gasto of this.gastos) {
    totais[gasto.categoria] = (totais[gasto.categoria] ?? 0) + gasto.valor;
  }

  const tabela = Object.entries(totais).map(([categoria, total]) => ({
    Categoria: categoria,
    "Total (R$)": total.toFixed(2),
  }));

  console.log("\n=== Resumo por Categoria ===");
  console.table(tabela);
}

totalGasto

Usa reduce para somar todos os valores — exatamente o método de array que estudamos:

totalGasto(): number {
  return this.gastos.reduce((acumulador, gasto) => acumulador + gasto.valor, 0);
}

filtrarPorCategoria

Usa filter para retornar apenas os gastos de uma categoria específica:

filtrarPorCategoria(categoria: Categoria): Gasto[] {
  return this.gastos.filter((g) => g.categoria === categoria);
}

Código completo: src/rastreador.ts

// src/rastreador.ts

import { Gasto, Categoria } from "./tipos";

export class RastreadorDeGastos {
  private gastos: Gasto[] = [];
  private proximoId: number = 1;

  adicionarGasto(descricao: string, valor: number, categoria: Categoria): void {
    if (valor <= 0) {
      throw new Error("O valor do gasto deve ser maior que zero.");
    }

    const novoGasto: Gasto = {
      id: this.proximoId++,
      descricao,
      valor,
      categoria,
      data: new Date().toLocaleDateString("pt-BR"),
    };

    this.gastos.push(novoGasto);
    console.log(`✓ "${descricao}" — R$ ${valor.toFixed(2)} adicionado.`);
  }

  listarGastos(): void {
    if (this.gastos.length === 0) {
      console.log("Nenhum gasto registrado ainda.");
      return;
    }

    const tabela = this.gastos.map((g) => ({
      ID: g.id,
      Descrição: g.descricao,
      "Valor (R$)": g.valor.toFixed(2),
      Categoria: g.categoria,
      Data: g.data,
    }));

    console.log("\n=== Todos os Gastos ===");
    console.table(tabela);
  }

  resumoPorCategoria(): void {
    if (this.gastos.length === 0) {
      console.log("Nenhum gasto registrado ainda.");
      return;
    }

    const totais: Record<string, number> = {};

    for (const gasto of this.gastos) {
      totais[gasto.categoria] = (totais[gasto.categoria] ?? 0) + gasto.valor;
    }

    const tabela = Object.entries(totais).map(([categoria, total]) => ({
      Categoria: categoria,
      "Total (R$)": total.toFixed(2),
    }));

    console.log("\n=== Resumo por Categoria ===");
    console.table(tabela);
  }

  totalGasto(): number {
    return this.gastos.reduce((acc, g) => acc + g.valor, 0);
  }

  filtrarPorCategoria(categoria: Categoria): Gasto[] {
    return this.gastos.filter((g) => g.categoria === categoria);
  }
}

3. O ponto de entrada: src/index.ts

O arquivo index.ts importa a classe, cria uma instância e usa todos os métodos:

// src/index.ts

import { RastreadorDeGastos } from "./rastreador";

const rastreador = new RastreadorDeGastos();

// Registrando gastos
rastreador.adicionarGasto("Almoço no restaurante", 45.5, "alimentação");
rastreador.adicionarGasto("Passagem de metrô", 4.4, "transporte");
rastreador.adicionarGasto("Cinema com amigos", 32.0, "lazer");
rastreador.adicionarGasto("Supermercado semanal", 187.3, "alimentação");
rastreador.adicionarGasto("Consulta médica", 250.0, "saúde");
rastreador.adicionarGasto("Uber para o trabalho", 23.7, "transporte");

// Listando todos
rastreador.listarGastos();

// Resumo por categoria
rastreador.resumoPorCategoria();

// Total geral
const total = rastreador.totalGasto();
console.log(`\nTotal gasto: R$ ${total.toFixed(2)}`);

// Filtrando por categoria
const gastosAlimentacao = rastreador.filtrarPorCategoria("alimentação");
console.log(
  `\nGastos com alimentação: ${gastosAlimentacao.length} registro(s)`
);

// Testando validação de erro
try {
  rastreador.adicionarGasto("Gasto inválido", -50, "outros");
} catch (erro) {
  if (erro instanceof Error) {
    console.log(`\nErro capturado: ${erro.message}`);
  }
}

Com os três arquivos prontos, vamos executar o projeto na próxima página.