Capítulo 20: Projeto Integrador: Portal de Gestão Financeira Corporativa
Capítulo 20: Projeto Integrador: Portal de Gestão Financeira Corporativa
Especialização em Front-End com Angular • Angular 18+ • Signals, Standalone Architecture, RxJS, NgRx e SSR
🗺️ Mapa Conceitual da Arquitetura do Portal Financeiro Corporativo
flowchart TD
Browser["Navegador do Usuário"] --> MainApp["App Standalone (provideRouter + provideHttpClient)"]
subgraph CORE_LAYER["Camada de Infraestrutura e Segurança"]
AuthG["authGuard (CanActivateFn)"]
Interceptors["Functional Interceptors (Bearer JWT + Latency Log)"]
Hydration["Client Hydration + Event Replay"]
end
MainApp --> CORE_LAYER
subgraph STATE_LAYER["Camada de Estado Global (NgRx SignalStore)"]
Store["FinanceiroStore"]
State["transacoes, saldoTotal, receitas, despesas, filtro"]
Methods["carregarTransacoes(), adicionarTransacao(), exportarRelatorio()"]
end
CORE_LAYER --> STATE_LAYER
subgraph UI_FEATURES["Camada de Apresentação (Standalone Components)"]
DashView["DashboardFinanceiroComponent"]
KpiCards["KpiCardsComponent (Signals Inputs)"]
DeferTable["@defer (on viewport) TransacoesTableComponent"]
ModalForm["NovaTransacaoModal (Reactive Forms Estrito)"]
end
STATE_LAYER --> UI_FEATURES
style Browser fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style CORE_LAYER fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style STATE_LAYER fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style UI_FEATURES fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. Visão Geral do Projeto Integrador
O Portal de Gestão Financeira Corporativa consolida e orquestra todas as tecnologias, conceitos e boas práticas dominados ao longo dos 20 capítulos desta especialização:
- Arquitetura 100% Standalone & Zoneless-Ready: Zero módulos NgModules legados.
- Reatividade Reativa com Angular Signals: Grafo reativo com
signal(),computed()eeffect(). - Comunicação por Signal Inputs e Outputs:
input.required(),output()emodel(). - Gerenciamento de Estado Centralizado com NgRx SignalStore: Stores leves, modulares e tipadas.
- Formulários Reativos Estritamente Tipados:
NonNullableFormBuildercom validações síncronas e assíncronas. - Segurança de Rota e Rede:
CanActivateFneHttpInterceptorFncom injeção automática de tokens JWT. - Performance Extrema: Deferrable Views (
@defer (on viewport)),NgOptimizedImagee suporte a SSR com hidratação não destrutiva. - Exportação de Dados Financeiros: Geração e download dinâmico de relatórios em formato JSON/CSV via API
Blob.
⚙️ 2. O Modelo de Dados e o Contrato de Transações
// models/financeiro.model.ts
export type TipoTransacao = 'RECEITA' | 'DESPESA';
export type CategoriaTransacao = 'FATURAMENTO' | 'SERVICOS' | 'INFRAESTRUTURA' | 'SALARIOS' | 'MARKETING';
export interface TransacaoFinanceira {
id: string;
descricao: string;
valor: number;
tipo: TipoTransacao;
categoria: CategoriaTransacao;
data: string; // ISO 8601
}
export interface MetricasFinanceiras {
saldoTotal: number;
totalReceitas: number;
totalDespesas: number;
taxaLucratividade: number;
}
📊 3. Central de Estado com NgRx SignalStore
A store centraliza as mutações de dados e cálculos de balanço financeiro em tempo real:
// stores/financeiro.store.ts
import { inject, computed } from '@angular/core';
import { signalStore, withState, withComputed, withMethods, patchState } from '@ngrx/signals';
import { HttpClient } from '@angular/common/http';
import { TransacaoFinanceira } from '../models/financeiro.model';
interface FinanceiroState {
transacoes: TransacaoFinanceira[];
filtroCategoria: string;
carregando: boolean;
}
const estadoInicial: FinanceiroState = {
transacoes: [
{ id: 'TX-101', descricao: 'Consultoria Cloud AWS', valor: 45000, tipo: 'RECEITA', categoria: 'SERVICOS', data: '2026-09-01' },
{ id: 'TX-102', descricao: 'Licenças de Software', valor: 12500, tipo: 'DESPESA', categoria: 'INFRAESTRUTURA', data: '2026-09-05' },
{ id: 'TX-103', descricao: 'Folha de Pagamento Engenharia', valor: 78000, tipo: 'DESPESA', categoria: 'SALARIOS', data: '2026-09-10' },
{ id: 'TX-104', descricao: 'Venda de Licença Enterprise', valor: 110000, tipo: 'RECEITA', categoria: 'FATURAMENTO', data: '2026-09-15' }
],
filtroCategoria: 'TODAS',
carregando: false
};
export const FinanceiroStore = signalStore(
{ providedIn: 'root' },
withState(estadoInicial),
withComputed(({ transacoes, filtroCategoria }) => {
const totalReceitas = computed(() =>
transacoes().filter(t => t.tipo === 'RECEITA').reduce((acc, t) => acc + t.valor, 0)
);
const totalDespesas = computed(() =>
transacoes().filter(t => t.tipo === 'DESPESA').reduce((acc, t) => acc + t.valor, 0)
);
const saldoTotal = computed(() => totalReceitas() - totalDespesas());
const transacoesFiltradas = computed(() => {
const cat = filtroCategoria();
return cat === 'TODAS'
? transacoes()
: transacoes().filter(t => t.categoria === cat);
});
return { totalReceitas, totalDespesas, saldoTotal, transacoesFiltradas };
}),
withMethods((store) => ({
adicionarTransacao(nova: Omit<TransacaoFinanceira, 'id'>): void {
const transacaoCompleta: TransacaoFinanceira = {
...nova,
id: `TX-${Date.now().toString().slice(-4)}`
};
patchState(store, state => ({
transacoes: [transacaoCompleta, ...state.transacoes]
}));
},
removerTransacao(id: string): void {
patchState(store, state => ({
transacoes: state.transacoes.filter(t => t.id !== id)
}));
},
filtrarPorCategoria(filtroCategoria: string): void {
patchState(store, { filtroCategoria });
},
exportarRelatorioJson(): void {
const dados = JSON.stringify(store.transacoes(), null, 2);
const blob = new Blob([dados], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `relatorio-financeiro-${new Date().toISOString().slice(0, 10)}.json`;
link.click();
URL.revokeObjectURL(url);
}
}))
);
💻 4. Componente Principal do Dashboard Financeiro
O componente integra os blocos @defer, formatação de moeda via CurrencyPipe e controles de interação reativa:
// features/dashboard/dashboard.component.ts
import { Component, inject } from '@angular/core';
import { CommonModule, CurrencyPipe, DatePipe } from '@angular/common';
import { FinanceiroStore } from '../../stores/financeiro.store';
import { ReactiveFormsModule, NonNullableFormBuilder, Validators } from '@angular/forms';
@Component({
selector: 'app-dashboard-financeiro',
standalone: true,
imports: [CommonModule, CurrencyPipe, DatePipe, ReactiveFormsModule],
template: `
<div class="dashboard-layout">
<header class="header-principal">
<h1>📊 Portal de Gestão Financeira Corporativa</h1>
<button class="btn-exportar" (click)="store.exportarRelatorioJson()">📥 Exportar Relatório (JSON)</button>
</header>
<!-- KPI Cards Reativos -->
<section class="kpi-grid">
<div class="kpi-card saldo" [class.negativo]="store.saldoTotal() < 0">
<small>Saldo Consolidado</small>
<h3>{{ store.saldoTotal() | currency:'BRL':'symbol':'1.2-2' }}</h3>
</div>
<div class="kpi-card receita">
<small>Total de Receitas</small>
<h3>{{ store.totalReceitas() | currency:'BRL':'symbol':'1.2-2' }}</h3>
</div>
<div class="kpi-card despesa">
<small>Total de Despesas</small>
<h3>{{ store.totalDespesas() | currency:'BRL':'symbol':'1.2-2' }}</h3>
</div>
</section>
<!-- Formulário de Nova Transação -->
<section class="secao-formulario card">
<h3>Lançar Nova Transação</h3>
<form [formGroup]="form" (ngSubmit)="aoSubmeter()" class="form-grid">
<div>
<label>Descrição:</label>
<input formControlName="descricao" type="text" placeholder="Ex: Servidores AWS" />
</div>
<div>
<label>Valor (R$):</label>
<input formControlName="valor" type="number" step="0.01" />
</div>
<div>
<label>Tipo:</label>
<select formControlName="tipo">
<option value="RECEITA">RECEITA (+)</option>
<option value="DESPESA">DESPESA (-)</option>
</select>
</div>
<div>
<label>Categoria:</label>
<select formControlName="categoria">
<option value="FATURAMENTO">Faturamento</option>
<option value="SERVICOS">Serviços</option>
<option value="INFRAESTRUTURA">Infraestrutura</option>
<option value="SALARIOS">Salários</option>
<option value="MARKETING">Marketing</option>
</select>
</div>
<button type="submit" [disabled]="form.invalid" class="btn-adicionar">+ Adicionar</button>
</form>
</section>
<!-- Tabela com Deferrable Views e Placeholder -->
<section class="secao-tabela card">
<div class="tabela-header">
<h3>Extrato de Transações</h3>
<div class="filtro">
<label>Filtrar Categoria: </label>
<select (change)="aoMudarFiltro($event)">
<option value="TODAS">Todas</option>
<option value="FATURAMENTO">Faturamento</option>
<option value="SERVICOS">Serviços</option>
<option value="INFRAESTRUTURA">Infraestrutura</option>
<option value="SALARIOS">Salários</option>
</select>
</div>
</div>
@defer (on viewport; prefetch on idle) {
<table class="tabela-financeira">
<thead>
<tr>
<th>Código</th>
<th>Data</th>
<th>Descrição</th>
<th>Categoria</th>
<th>Valor</th>
<th>Ações</th>
</tr>
</thead>
<tbody>
@for (tx of store.transacoesFiltradas(); track tx.id) {
<tr [class.linha-despesa]="tx.tipo === 'DESPESA'">
<td><code>{{ tx.id }}</code></td>
<td>{{ tx.data | date:'dd/MM/yyyy' }}</td>
<td>{{ tx.descricao }}</td>
<td><span class="badge">{{ tx.categoria }}</span></td>
<td class="valor-col">
{{ tx.tipo === 'RECEITA' ? '+' : '-' }} {{ tx.valor | currency:'BRL' }}
</td>
<td>
<button class="btn-excluir" (click)="store.removerTransacao(tx.id)">Remover</button>
</td>
</tr>
} @empty {
<tr><td colspan="6" class="sem-dados">Nenhuma transação encontrada no filtro selecionado.</td></tr>
}
</tbody>
</table>
} @placeholder (minimum 300ms) {
<div class="skeleton-placeholder">
<p>Carregando extrato contábil interativo...</p>
</div>
}
</section>
</div>
`
})
export class DashboardFinanceiroComponent {
readonly store = inject(FinanceiroStore);
private fb = inject(NonNullableFormBuilder);
form = this.fb.group({
descricao: ['', [Validators.required, Validators.minLength(3)]],
valor: [0, [Validators.required, Validators.min(0.01)]],
tipo: ['RECEITA' as 'RECEITA' | 'DESPESA', [Validators.required]],
categoria: ['FATURAMENTO' as any, [Validators.required]]
});
aoSubmeter(): void {
if (this.form.valid) {
const val = this.form.getRawValue();
this.store.adicionarTransacao({
descricao: val.descricao,
valor: val.valor,
tipo: val.tipo,
categoria: val.categoria,
data: new Date().toISOString()
});
this.form.reset({ descricao: '', valor: 0, tipo: 'RECEITA', categoria: 'FATURAMENTO' });
}
}
aoMudarFiltro(event: Event): void {
const select = event.target as HTMLSelectElement;
this.store.filtrarPorCategoria(select.value);
}
}
⚖️ 5. Conclusão da Especialização e Próximos Passos na Carreira
Ao completar esta jornada de 20 capítulos no Padrão Ouro Educacional, você adquiriu domínio pleno sobre:
- Modern Frontend Engineering: Angular 18/19 Standalone, eliminando módulos pesados e adotando componentes atômicos.
- Reatividade de Próxima Geração: Signals, reatividade fina síncrona sem overhead de Zone.js.
- Mestria em Streams Assíncronas: RxJS avançado para redes, cancelamentos e concorrência.
- Arquitetura de Estado Previsível: NgRx SignalStore desacoplado, imutável e altamente testável.
- Excelência de Produção: Server-Side Rendering (SSR), hidratação com Event Replay e Deferrable Views para pontuações máximas no Google Core Web Vitals.