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.