Summary: Opening 96 Blocks Project


Summary: Analyzing PROD-96Blocks Project for Improvements


Summary: Opening and Analyzing 96 Blocks Xcode Project


Summary: 96 Blocks Project Analysis and Bug Fix Preparation


Summary: 96Blocks iOS: Critical Security & Bug Fixes


Summary: iOS 96Blocks Project Analysis & Critical Bug Fixes


Summary: Analyze 96Blocks iOS project and fix critical issues


Summary: 96 Blocks iOS Project Analysis and Critical Phase 1 Fixes


/Users/neog/Apps/macOS/BCBDashboard-macOS/BCBDashboard.xcodeproj  analise o projeto.


Summary: 96 Blocks iOS: Security Fix & Critical Bug Repairs


Summary: 96Blocks iOS: Critical Bug Fixes Phase 1


Summary: 96Blocks iOS Project Analysis and Critical Bug Fixes


Bash

List BCBDashboard project contents


Summary: 96Blocks iOS Project: Critical Phase 1 Bug Fixes


total 88
drwxr-xr-x  16 neog  staff    512 Jan 11 21:20 .
drwxr-xr-x@ 40 neog  staff   1280 Jan 11 21:20 ..
-rw-r--r--@  1 neog  staff   6148 Jan 11 21:22 .DS_Store
drwxr-xr-x  12 neog  staff    384 Jan  9 16:08 .build
drwxr-xr-x   4 neog  staff    128 Jan  9 15:42 .swiftpm
drwxr-xr-x   5 neog  staff    160 Jan  9 16:14 Assets.xcassets
-rw-r--r--   1 neog  staff    367 Jan  9 22:13 BCBDashboard.entitlements
drwxr-xr-x@  5 neog  staff    160 Jan  9 22:40 BCBDashboard.xcodeproj
-rw-r--r--   1 neog  staff  22057 Jan  9 22:41 BCBDashboardApp.swift
-rw-r--r--   1 neog  staff   3175 Jan  9 15:40 CLAUDE.md
drwxr-xr-x   3 neog  staff     96 Jan  9 17:52 Extensions
drwxr-xr-x   5 neog  staff    160 Jan  9 16:34 Models
-rw-r--r--@  1 neog  staff   1191 Jan  9 15:47 Package.swift
drwxr-xr-x   5 neog  staff    160 Jan  9 22:37 Services
drwxr-xr-x   3 neog  staff     96 Jan  9 22:42 ViewModels
drwxr-xr-x  11 neog  staff    352 Jan  9 18:22 Views


Summary: iOS 96Blocks Project Critical Bug Fixes


Summary: 96Blocks iOS: Critical Fixes & Phase 2 High Priority Issues


Summary: 96Blocks iOS Critical Fixes & Phase 2 Improvements


Summary: 96 Blocks iOS: Critical Fixes & Phase 2 Start


Summary: 96Blocks iOS: Critical Fixes & Phase 2 Implementation


     1→# BCB Dashboard - macOS App
     2→
     3→Dashboard nativo macOS para consumir APIs públicas do Banco Central do Brasil.
     4→
     5→## Quick Start
     6→
     7→```bash
     8→# 1. Abrir pasta no Xcode
     9→open /Users/neog/Apps/macOS/BCBDashboard-macOS
    10→
    11→# 2. Criar novo projeto Xcode
    12→# File > New > Project > macOS > App
    13→# Product Name: BCBDashboard
    14→# Organization: com.neog
    15→# Interface: SwiftUI
    16→# Language: Swift
    17→# Storage: None
    18→
    19→# 3. Adicionar todos os arquivos .swift ao projeto
    20→# Drag & drop das pastas: Models, Services, ViewModels, Views, Extensions
    21→
    22→# 4. Build & Run (⌘R)
    23→```
    24→
    25→## Estrutura do Projeto
    26→
    27→```
    28→BCBDashboard-macOS/
    29→├── BCBDashboardApp.swift       # Entry point
    30→├── Models/
    31→│   ├── Currency.swift          # Cotações PTAX
    32→│   ├── TimeSeries.swift        # Séries SGS
    33→│   └── Institution.swift       # Instituições + PIX + Focus
    34→├── Services/
    35→│   └── BCBAPIClient.swift      # Cliente HTTP + Cache
    36→├── ViewModels/
    37→│   └── DashboardViewModel.swift # Estado principal
    38→├── Views/
    39→│   ├── ContentView.swift       # NavigationSplitView
    40→│   ├── Dashboard/              # Overview + Cards + Charts
    41→│   ├── Currency/               # Lista de moedas
    42→│   ├── Rates/                  # SELIC, IPCA, CDI
    43→│   ├── Institutions/           # Busca instituições
    44→│   ├── PIX/                    # Estatísticas PIX
    45→│   └── Expectations/           # Focus (expectativas)
    46→└── Extensions/
    47→    └── Extensions.swift        # Helpers (Color, Double, Date)
    48→```
    49→
    50→## APIs Integradas
    51→
    52→### 1. PTAX (Câmbio)
    53→- Base: `https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/`
    54→- Endpoints: Moedas, CotacaoDolarDia, CotacaoMoedaDia, etc.
    55→
    56→### 2. SGS (Séries Temporais)
    57→- Base: `https://api.bcb.gov.br/dados/serie/bcdata.sgs.{codigo}/dados`
    58→- Códigos: 11 (SELIC), 432 (SELIC meta), 433 (IPCA), 13522 (IPCA 12m), 4389 (CDI)
    59→
    60→### 3. BcBase (Instituições)
    61→- Base: `https://olinda.bcb.gov.br/olinda/servico/BcBase/versao/v2/odata/`
    62→
    63→### 4. Expectativas (Focus)
    64→- Base: `https://olinda.bcb.gov.br/olinda/servico/Expectativas/versao/v1/odata/`
    65→
    66→## Configurações Necessárias
    67→
    68→### Info.plist (App Transport Security)
    69→```xml
    70→<key>NSAppTransportSecurity</key>
    71→<dict>
    72→    <key>NSAllowsArbitraryLoads</key>
    73→    <true/>
    74→</dict>
    75→```
    76→
    77→### Build Settings
    78→- Deployment Target: macOS 14.0
    79→- Swift Language Version: 5.9
    80→
    81→## Features
    82→
    83→- Dashboard com cotações em tempo real
    84→- Gráficos com Swift Charts
    85→- Histórico de câmbio e taxas
    86→- Busca de instituições financeiras
    87→- Expectativas de mercado (Focus)
    88→- Auto-refresh configurável
    89→- Cache local de dados
    90→- Keyboard shortcuts (⌘R refresh)
    91→
    92→## Testes
    93→
    94→```bash
    95→# Testar APIs diretamente
    96→curl "https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarDia(dataCotacao=@d)?@d='01-09-2026'&\$format=json"
    97→
    98→curl "https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados/ultimos/1?formato=json"
    99→```
   100→
   101→## Troubleshooting
   102→
   103→1. **Erro de rede**: Verificar conexão e se APIs do BCB estão online
   104→2. **Dados vazios**: APIs não retornam dados em fins de semana/feriados
   105→3. **Build error**: Verificar se todos os arquivos estão no target
   106→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

# BCB Dashboard - macOS App

Dashboard nativo macOS para consumir APIs públicas do Banco Central do Brasil.

## Quick Start

```bash
# 1. Abrir pasta no Xcode
open /Users/neog/Apps/macOS/BCBDashboard-macOS

# 2. Criar novo projeto Xcode
# File > New > Project > macOS > App
# Product Name: BCBDashboard
# Organization: com.neog
# Interface: SwiftUI
# Language: Swift
# Storage: None

# 3. Adicionar todos os arquivos .swift ao projeto
# Drag & drop das pastas: Models, Services, ViewModels, Views, Extensions

# 4. Build & Run (⌘R)
```

## Estrutura do Projeto

```
BCBDashboard-macOS/
├── BCBDashboardApp.swift       # Entry point
├── Models/
│   ├── Currency.swift          # Cotações PTAX
│   ├── TimeSeries.swift        # Séries SGS
│   └── Institution.swift       # Instituições + PIX + Focus
├── Services/
│   └── BCBAPIClient.swift      # Cliente HTTP + Cache
├── ViewModels/
│   └── DashboardViewModel.swift # Estado principal
├── Views/
│   ├── ContentView.swift       # NavigationSplitView
│   ├── Dashboard/              # Overview + Cards + Charts
│   ├── Currency/               # Lista de moedas
│   ├── Rates/                  # SELIC, IPCA, CDI
│   ├── Institutions/           # Busca instituições
│   ├── PIX/                    # Estatísticas PIX
│   └── Expectations/           # Focus (expectativas)
└── Extensions/
    └── Extensions.swift        # Helpers (Color, Double, Date)
```

## APIs Integradas

### 1. PTAX (Câmbio)
- Base: `https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/`
- Endpoints: Moedas, CotacaoDolarDia, CotacaoMoedaDia, etc.

### 2. SGS (Séries Temporais)
- Base: `https://api.bcb.gov.br/dados/serie/bcdata.sgs.{codigo}/dados`
- Códigos: 11 (SELIC), 432 (SELIC meta), 433 (IPCA), 13522 (IPCA 12m), 4389 (CDI)

### 3. BcBase (Instituições)
- Base: `https://olinda.bcb.gov.br/olinda/servico/BcBase/versao/v2/odata/`

### 4. Expectativas (Focus)
- Base: `https://olinda.bcb.gov.br/olinda/servico/Expectativas/versao/v1/odata/`

## Configurações Necessárias

### Info.plist (App Transport Security)
```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
```

### Build Settings
- Deployment Target: macOS 14.0
- Swift Language Version: 5.9

## Features

- Dashboard com cotações em tempo real
- Gráficos com Swift Charts
- Histórico de câmbio e taxas
- Busca de instituições financeiras
- Expectativas de mercado (Focus)
- Auto-refresh configurável
- Cache local de dados
- Keyboard shortcuts (⌘R refresh)

## Testes

```bash
# Testar APIs diretamente
curl "https://olinda.bcb.gov.br/olinda/servico/PTAX/versao/v1/odata/CotacaoDolarDia(dataCotacao=@d)?@d='01-09-2026'&\$format=json"

curl "https://api.bcb.gov.br/dados/serie/bcdata.sgs.432/dados/ultimos/1?formato=json"
```

## Troubleshooting

1. **Erro de rede**: Verificar conexão e se APIs do BCB estão online
2. **Dados vazios**: APIs não retornam dados em fins de semana/feriados
3. **Build error**: Verificar se todos os arquivos estão no target


     1→import SwiftUI
     2→import ServiceManagement
     3→
     4→@main
     5→struct BCBDashboardApp: App {
     6→    @StateObject private var dashboardViewModel = DashboardViewModel()
     7→    @StateObject private var logManager = LogManager.shared
     8→    @AppStorage("showMenuBarWidget") private var showMenuBarWidget: Bool = true
     9→
    10→    var body: some Scene {
    11→        WindowGroup {
    12→            ContentView()
    13→                .environmentObject(dashboardViewModel)
    14→        }
    15→        .windowStyle(.automatic)
    16→        .defaultSize(width: 1200, height: 800)
    17→        .commands {
    18→            CommandGroup(replacing: .newItem) { }
    19→
    20→            CommandMenu("Data") {
    21→                Button("Refresh All") {
    22→                    Task {
    23→                        await dashboardViewModel.refreshAll()
    24→                    }
    25→                }
    26→                .keyboardShortcut("r", modifiers: .command)
    27→
    28→                Divider()
    29→
    30→                Button("Clear Cache") {
    31→                    dashboardViewModel.clearCache()
    32→                }
    33→                .keyboardShortcut("k", modifiers: [.command, .shift])
    34→            }
    35→
    36→            CommandMenu("View") {
    37→                Toggle("Show Menu Bar Widget", isOn: $showMenuBarWidget)
    38→            }
    39→
    40→            CommandMenu("Debug") {
    41→                Button("Show Log Viewer") {
    42→                    NSApp.sendAction(#selector(NSApplication.showLogWindow(_:)), to: nil, from: nil)
    43→                }
    44→                .keyboardShortcut("l", modifiers: [.command, .option])
    45→
    46→                Divider()
    47→
    48→                Button("Clear Logs") {
    49→                    logManager.clear()
    50→                }
    51→
    52→                Toggle("Enable Logging", isOn: $logManager.isEnabled)
    53→
    54→                Divider()
    55→
    56→                Button("Export Logs...") {
    57→                    exportLogs()
    58→                }
    59→            }
    60→        }
    61→
    62→        Settings {
    63→            SettingsView()
    64→        }
    65→
    66→        // Log Viewer Window
    67→        Window("Log Viewer", id: "log-viewer") {
    68→            LogViewerWindow()
    69→        }
    70→        .defaultSize(width: 900, height: 500)
    71→        .keyboardShortcut("l", modifiers: [.command, .option])
    72→
    73→        // Menu Bar Widget
    74→        MenuBarExtra(isInserted: $showMenuBarWidget) {
    75→            MenuBarWidgetView()
    76→                .environmentObject(dashboardViewModel)
    77→        } label: {
    78→            MenuBarLabel(viewModel: dashboardViewModel)
    79→        }
    80→        .menuBarExtraStyle(.window)
    81→    }
    82→
    83→    private func exportLogs() {
    84→        let content = logManager.export()
    85→        let panel = NSSavePanel()
    86→        panel.nameFieldStringValue = "bcb-dashboard-logs-\(Date().ISO8601Format()).txt"
    87→        panel.allowedContentTypes = [.plainText]
    88→
    89→        if panel.runModal() == .OK, let url = panel.url {
    90→            try? content.write(to: url, atomically: true, encoding: .utf8)
    91→        }
    92→    }
    93→}
    94→
    95→// Extension to handle log window opening
    96→extension NSApplication {
    97→    @objc func showLogWindow(_ sender: Any?) {
    98→        // Open the log viewer window
    99→        if let window = windows.first(where: { $0.title == "Log Viewer" }) {
   100→            window.makeKeyAndOrderFront(nil)
   101→        } else {
   102→            // Use environment to open window
   103→            NotificationCenter.default.post(name: NSNotification.Name("OpenLogViewer"), object: nil)
   104→        }
   105→    }
   106→}
   107→
   108→// MARK: - Menu Bar Label
   109→
   110→struct MenuBarLabel: View {
   111→    @ObservedObject var viewModel: DashboardViewModel
   112→
   113→    var body: some View {
   114→        HStack(spacing: 8) {
   115→            // USD Value
   116→            HStack(spacing: 2) {
   117→                Text("$")
   118→                    .font(.system(size: 10, weight: .medium))
   119→                Text(viewModel.dollarQuote?.sellRate.toMenuBarString() ?? "--")
   120→                    .font(.system(size: 11, weight: .semibold, design: .rounded))
   121→            }
   122→
   123→            // Separator
   124→            Text("│")
   125→                .font(.system(size: 9))
   126→                .foregroundStyle(.secondary)
   127→
   128→            // SELIC Value
   129→            HStack(spacing: 1) {
   130→                Text(String(format: "%.1f", viewModel.selicTarget?.numericValue ?? 0.0))
   131→                    .font(.system(size: 11, weight: .semibold, design: .rounded))
   132→                Text("%")
   133→                    .font(.system(size: 9, weight: .medium))
   134→            }
   135→        }
   136→    }
   137→}
   138→
   139→// MARK: - Menu Bar Widget View
   140→
   141→struct MenuBarWidgetView: View {
   142→    @EnvironmentObject var viewModel: DashboardViewModel
   143→    @Environment(\.openWindow) private var openWindow
   144→
   145→    var body: some View {
   146→        VStack(spacing: 0) {
   147→            // Header
   148→            HStack {
   149→                Image(systemName: "building.columns.fill")
   150→                    .foregroundStyle(Color.bcbBlue)
   151→                Text("BCB Dashboard")
   152→                    .font(.headline)
   153→                Spacer()
   154→                if viewModel.isLoading {
   155→                    ProgressView()
   156→                        .controlSize(.small)
   157→                }
   158→            }
   159→            .padding()
   160→            .background(Color(NSColor.controlBackgroundColor))
   161→
   162→            Divider()
   163→
   164→            // Currency Section
   165→            VStack(spacing: 12) {
   166→                MenuBarRateRow(
   167→                    icon: "dollarsign.circle.fill",
   168→                    iconColor: .bcbGreen,
   169→                    title: "Dólar (USD)",
   170→                    value: viewModel.dollarQuote?.sellRate.toBRL() ?? "--",
   171→                    subtitle: viewModel.dollarQuote?.formattedTime ?? "",
   172→                    change: viewModel.dollarChange
   173→                )
   174→
   175→                MenuBarRateRow(
   176→                    icon: "eurosign.circle.fill",
   177→                    iconColor: .bcbBlue,
   178→                    title: "Euro (EUR)",
   179→                    value: viewModel.euroQuote?.sellRate.toBRL() ?? "--",
   180→                    subtitle: viewModel.euroQuote?.formattedTime ?? "",
   181→                    change: nil
   182→                )
   183→
   184→                Divider()
   185→                    .padding(.vertical, 4)
   186→
   187→                MenuBarRateRow(
   188→                    icon: "percent",
   189→                    iconColor: .bcbPurple,
   190→                    title: "SELIC Meta",
   191→                    value: String(format: "%.2f%%", viewModel.selicTarget?.numericValue ?? 0.0),
   192→                    subtitle: viewModel.selicTarget?.date ?? "",
   193→                    change: nil
   194→                )
   195→
   196→                MenuBarRateRow(
   197→                    icon: "chart.line.uptrend.xyaxis",
   198→                    iconColor: .bcbYellow,
   199→                    title: "IPCA 12m",
   200→                    value: String(format: "%.2f%%", viewModel.ipca12m?.numericValue ?? 0.0),
   201→                    subtitle: viewModel.ipca12m?.date ?? "",
   202→                    change: nil
   203→                )
   204→
   205→                if let selicExp = viewModel.focusStats.latestSelic {
   206→                    Divider()
   207→                        .padding(.vertical, 4)
   208→
   209→                    MenuBarRateRow(
   210→                        icon: "chart.bar.fill",
   211→                        iconColor: .bcbPink,
   212→                        title: "SELIC Esperada",
   213→                        value: String(format: "%.2f%%", selicExp.median),
   214→                        subtitle: selicExp.formattedMeeting,
   215→                        change: nil
   216→                    )
   217→                }
   218→            }
   219→            .padding()
   220→
   221→            Divider()
   222→
   223→            // Footer with actions
   224→            HStack {
   225→                if let lastUpdate = viewModel.lastUpdate {
   226→                    Text(lastUpdate.toRelativeString())
   227→                        .font(.caption2)
   228→                        .foregroundStyle(.secondary)
   229→                }
   230→
   231→                Spacer()
   232→
   233→                Button {
   234→                    Task {
   235→                        await viewModel.refreshAll()
   236→                    }
   237→                } label: {
   238→                    Image(systemName: "arrow.clockwise")
   239→                        .font(.caption)
   240→                }
   241→                .buttonStyle(.borderless)
   242→                .disabled(viewModel.isLoading)
   243→
   244→                Divider()
   245→                    .frame(height: 16)
   246→
   247→                Button {
   248→                    NSApplication.shared.activate(ignoringOtherApps: true)
   249→                    if let window = NSApplication.shared.windows.first(where: { $0.title.contains("BCB") || $0.contentView != nil }) {
   250→                        window.makeKeyAndOrderFront(nil)
   251→                    }
   252→                } label: {
   253→                    Image(systemName: "macwindow")
   254→                        .font(.caption)
   255→                }
   256→                .buttonStyle(.borderless)
   257→
   258→                Divider()
   259→                    .frame(height: 16)
   260→
   261→                Button {
   262→                    NSApplication.shared.terminate(nil)
   263→                } label: {
   264→                    Image(systemName: "power")
   265→                        .font(.caption)
   266→                }
   267→                .buttonStyle(.borderless)
   268→            }
   269→            .padding(.horizontal)
   270→            .padding(.vertical, 8)
   271→            .background(Color(NSColor.controlBackgroundColor))
   272→        }
   273→        .frame(width: 280)
   274→    }
   275→}
   276→
   277→// MARK: - Menu Bar Rate Row
   278→
   279→struct MenuBarRateRow: View {
   280→    let icon: String
   281→    let iconColor: Color
   282→    let title: String
   283→    let value: String
   284→    let subtitle: String
   285→    let change: Double?
   286→
   287→    var body: some View {
   288→        HStack {
   289→            Image(systemName: icon)
   290→                .font(.title3)
   291→                .foregroundStyle(iconColor)
   292→                .frame(width: 24)
   293→
   294→            VStack(alignment: .leading, spacing: 2) {
   295→                Text(title)
   296→                    .font(.caption)
   297→                    .foregroundStyle(.secondary)
   298→                Text(subtitle)
   299→                    .font(.caption2)
   300→                    .foregroundStyle(.tertiary)
   301→            }
   302→
   303→            Spacer()
   304→
   305→            VStack(alignment: .trailing, spacing: 2) {
   306→                Text(value)
   307→                    .font(.system(.body, design: .rounded))
   308→                    .fontWeight(.semibold)
   309→
   310→                if let change = change, change != 0 {
   311→                    HStack(spacing: 2) {
   312→                        Image(systemName: change >= 0 ? "arrow.up.right" : "arrow.down.right")
   313→                            .font(.caption2)
   314→                        Text(String(format: "%.2f%%", abs(change)))
   315→                            .font(.caption2)
   316→                    }
   317→                    .foregroundStyle(change >= 0 ? Color.bcbGreen : Color.bcbRed)
   318→                }
   319→            }
   320→        }
   321→    }
   322→}
   323→
   324→struct SettingsView: View {
   325→    @AppStorage("refreshInterval") private var refreshInterval: Int = 5
   326→    @AppStorage("autoLoadFocus") private var autoLoadFocus: Bool = true
   327→    @AppStorage("autoLoadPIX") private var autoLoadPIX: Bool = true
   328→    @AppStorage("autoLoadInstitutions") private var autoLoadInstitutions: Bool = false
   329→    @AppStorage("cacheExpirationMinutes") private var cacheExpiration: Int = 5
   330→    @AppStorage("showDecimalPlaces") private var decimalPlaces: Int = 2
   331→    @AppStorage("compactMode") private var compactMode: Bool = false
   332→    @AppStorage("showMenuBarWidget") private var showMenuBarWidget: Bool = true
   333→    @StateObject private var notificationService = NotificationService.shared
   334→    @State private var launchAtLogin = SMAppService.mainApp.status == .enabled
   335→
   336→    var body: some View {
   337→        TabView {
   338→            // General Settings
   339→            Form {
   340→                Section("Inicialização") {
   341→                    Toggle("Abrir ao iniciar sessão", isOn: $launchAtLogin)
   342→                        .onChange(of: launchAtLogin) { _, newValue in
   343→                            do {
   344→                                if newValue {
   345→                                    try SMAppService.mainApp.register()
   346→                                } else {
   347→                                    try SMAppService.mainApp.unregister()
   348→                                }
   349→                            } catch {
   350→                                print("Failed to update login item: \(error)")
   351→                                // Revert the toggle if it failed
   352→                                launchAtLogin = SMAppService.mainApp.status == .enabled
   353→                            }
   354→                        }
   355→
   356→                    Toggle("Mostrar na barra de menus", isOn: $showMenuBarWidget)
   357→
   358→                    HStack {
   359→                        Image(systemName: "info.circle")
   360→                            .foregroundStyle(.secondary)
   361→                        Text("O app continuará rodando na barra de menus mesmo com a janela fechada")
   362→                            .font(.caption)
   363→                            .foregroundStyle(.secondary)
   364→                    }
   365→                }
   366→
   367→                Section("Atualização Automática") {
   368→                    Picker("Intervalo de atualização", selection: $refreshInterval) {
   369→                        Text("30 segundos").tag(0) // Special case: 0.5 min
   370→                        Text("1 minuto").tag(1)
   371→                        Text("2 minutos").tag(2)
   372→                        Text("5 minutos").tag(5)
   373→                        Text("10 minutos").tag(10)
   374→                        Text("15 minutos").tag(15)
   375→                        Text("30 minutos").tag(30)
   376→                        Text("1 hora").tag(60)
   377→                        Text("Desativado").tag(-1)
   378→                    }
   379→                    .pickerStyle(.menu)
   380→
   381→                    if refreshInterval >= 0 {
   382→                        HStack {
   383→                            Image(systemName: "info.circle")
   384→                                .foregroundStyle(.secondary)
   385→                            Text(refreshInterval == 0 ? "Atualiza a cada 30 segundos" : "Atualiza a cada \(refreshInterval) minuto(s)")
   386→                                .font(.caption)
   387→                                .foregroundStyle(.secondary)
   388→                        }
   389→                    }
   390→                }
   391→
   392→                Section("Cache") {
   393→                    Picker("Expiração do cache", selection: $cacheExpiration) {
   394→                        Text("1 minuto").tag(1)
   395→                        Text("5 minutos").tag(5)
   396→                        Text("15 minutos").tag(15)
   397→                        Text("30 minutos").tag(30)
   398→                        Text("1 hora").tag(60)
   399→                    }
   400→                    .pickerStyle(.menu)
   401→
   402→                    HStack {
   403→                        Image(systemName: "info.circle")
   404→                            .foregroundStyle(.secondary)
   405→                        Text("Dados em cache são reutilizados para evitar requisições repetidas")
   406→                            .font(.caption)
   407→                            .foregroundStyle(.secondary)
   408→                    }
   409→                }
   410→
   411→                Section("Sobre") {
   412→                    LabeledContent("Versão", value: "1.1.0")
   413→                    LabeledContent("Fonte de dados", value: "Banco Central do Brasil")
   414→                    LabeledContent("APIs utilizadas", value: "PTAX, SGS, SPI, Focus")
   415→
   416→                    Link(destination: URL(string: "https://dadosabertos.bcb.gov.br")!) {
   417→                        HStack {
   418→                            Text("Portal de Dados Abertos BCB")
   419→                            Spacer()
   420→                            Image(systemName: "arrow.up.right.square")
   421→                        }
   422→                    }
   423→                }
   424→            }
   425→            .formStyle(.grouped)
   426→            .tabItem {
   427→                Label("Geral", systemImage: "gear")
   428→            }
   429→
   430→            // Data Settings
   431→            Form {
   432→                Section("Carregamento Automático") {
   433→                    Toggle("Carregar Focus (Expectativas) no início", isOn: $autoLoadFocus)
   434→                    Toggle("Carregar PIX no início", isOn: $autoLoadPIX)
   435→                    Toggle("Carregar Instituições no início", isOn: $autoLoadInstitutions)
   436→
   437→                    HStack {
   438→                        Image(systemName: "info.circle")
   439→                            .foregroundStyle(.secondary)
   440→                        Text("Desativar carregamento automático economiza dados e acelera o início")
   441→                            .font(.caption)
   442→                            .foregroundStyle(.secondary)
   443→                    }
   444→                }
   445→
   446→                Section("Exibição") {
   447→                    Picker("Casas decimais", selection: $decimalPlaces) {
   448→                        Text("2 casas (R$ 5,37)").tag(2)
   449→                        Text("3 casas (R$ 5,370)").tag(3)
   450→                        Text("4 casas (R$ 5,3700)").tag(4)
   451→                    }
   452→                    .pickerStyle(.menu)
   453→
   454→                    Toggle("Modo compacto", isOn: $compactMode)
   455→
   456→                    HStack {
   457→                        Image(systemName: "info.circle")
   458→                            .foregroundStyle(.secondary)
   459→                        Text("Modo compacto reduz espaçamentos e tamanho de fontes")
   460→                            .font(.caption)
   461→                            .foregroundStyle(.secondary)
   462→                    }
   463→                }
   464→
   465→                Section("Dados") {
   466→                    HStack {
   467→                        VStack(alignment: .leading, spacing: 4) {
   468→                            Text("APIs do Banco Central")
   469→                                .font(.subheadline)
   470→                            Text("Dados atualizados em tempo real")
   471→                                .font(.caption)
   472→                                .foregroundStyle(.secondary)
   473→                        }
   474→
   475→                        Spacer()
   476→
   477→                        Circle()
   478→                            .fill(.green)
   479→                            .frame(width: 8, height: 8)
   480→                        Text("Online")
   481→                            .font(.caption)
   482→                            .foregroundStyle(.secondary)
   483→                    }
   484→                }
   485→            }
   486→            .formStyle(.grouped)
   487→            .tabItem {
   488→                Label("Dados", systemImage: "chart.bar.doc.horizontal")
   489→            }
   490→
   491→            // Notification Settings
   492→            Form {
   493→                Section("Notificações") {
   494→                    Toggle("Ativar notificações", isOn: $notificationService.notificationsEnabled)
   495→                        .onChange(of: notificationService.notificationsEnabled) { _, _ in
   496→                            notificationService.saveSettings()
   497→                        }
   498→
   499→                    if !notificationService.isAuthorized {
   500→                        HStack {
   501→                            Image(systemName: "exclamationmark.triangle.fill")
   502→                                .foregroundStyle(.yellow)
   503→                            Text("Permissão de notificações não concedida")
   504→                                .font(.caption)
   505→                                .foregroundStyle(.secondary)
   506→                        }
   507→
   508→                        Button("Solicitar Permissão") {
   509→                            Task {
   510→                                await notificationService.requestAuthorization()
   511→                            }
   512→                        }
   513→                    }
   514→                }
   515→
   516→                Section("Alertas de Câmbio") {
   517→                    Toggle("Notificar qualquer mudança", isOn: $notificationService.notifyOnAnyChange)
   518→                        .onChange(of: notificationService.notifyOnAnyChange) { _, _ in
   519→                            notificationService.saveSettings()
   520→                        }
   521→
   522→                    if !notificationService.notifyOnAnyChange {
   523→                        HStack {
   524→                            Text("Variação mínima:")
   525→                            Slider(value: $notificationService.dollarChangeThreshold, in: 0.1...5.0, step: 0.1)
   526→                                .onChange(of: notificationService.dollarChangeThreshold) { _, _ in
   527→                                    notificationService.saveSettings()
   528→                                }
   529→                            Text(String(format: "%.1f%%", notificationService.dollarChangeThreshold))
   530→                                .monospacedDigit()
   531→                                .frame(width: 50)
   532→                        }
   533→                    }
   534→                }
   535→
   536→                Section("Teste") {
   537→                    Button("Enviar Notificação de Teste") {
   538→                        notificationService.sendTestNotification()
   539→                    }
   540→                    .disabled(!notificationService.isAuthorized || !notificationService.notificationsEnabled)
   541→
   542→                    Button("Limpar Todas as Notificações") {
   543→                        notificationService.clearAllNotifications()
   544→                    }
   545→                }
   546→            }
   547→            .formStyle(.grouped)
   548→            .tabItem {
   549→                Label("Notificações", systemImage: "bell")
   550→            }
   551→
   552→            // Keyboard Shortcuts
   553→            Form {
   554→                Section("Atalhos de Teclado") {
   555→                    KeyboardShortcutRow(keys: "⌘ R", description: "Atualizar todos os dados")
   556→                    KeyboardShortcutRow(keys: "⌘ ⇧ K", description: "Limpar cache")
   557→                    KeyboardShortcutRow(keys: "⌘ ,", description: "Abrir configurações")
   558→                    KeyboardShortcutRow(keys: "⌘ 1-6", description: "Navegar entre abas")
   559→                }
   560→
   561→                Section("Navegação") {
   562→                    KeyboardShortcutRow(keys: "⌘ [", description: "Voltar")
   563→                    KeyboardShortcutRow(keys: "⌘ ]", description: "Avançar")
   564→                    KeyboardShortcutRow(keys: "⌘ W", description: "Fechar janela")
   565→                }
   566→            }
   567→            .formStyle(.grouped)
   568→            .tabItem {
   569→                Label("Atalhos", systemImage: "keyboard")
   570→            }
   571→        }
   572→        .frame(width: 500, height: 400)
   573→    }
   574→}
   575→
   576→struct KeyboardShortcutRow: View {
   577→    let keys: String
   578→    let description: String
   579→
   580→    var body: some View {
   581→        HStack {
   582→            Text(keys)
   583→                .font(.system(.body, design: .monospaced))
   584→                .padding(.horizontal, 8)
   585→                .padding(.vertical, 4)
   586→                .background(Color(NSColor.controlBackgroundColor))
   587→                .clipShape(RoundedRectangle(cornerRadius: 6))
   588→
   589→            Text(description)
   590→                .foregroundStyle(.secondary)
   591→
   592→            Spacer()
   593→        }
   594→    }
   595→}
   596→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import SwiftUI
import ServiceManagement

@main
struct BCBDashboardApp: App {
    @StateObject private var dashboardViewModel = DashboardViewModel()
    @StateObject private var logManager = LogManager.shared
    @AppStorage("showMenuBarWidget") private var showMenuBarWidget: Bool = true

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(dashboardViewModel)
        }
        .windowStyle(.automatic)
        .defaultSize(width: 1200, height: 800)
        .commands {
            CommandGroup(replacing: .newItem) { }

            CommandMenu("Data") {
                Button("Refresh All") {
                    Task {
                        await dashboardViewModel.refreshAll()
                    }
                }
                .keyboardShortcut("r", modifiers: .command)

                Divider()

                Button("Clear Cache") {
                    dashboardViewModel.clearCache()
                }
                .keyboardShortcut("k", modifiers: [.command, .shift])
            }

            CommandMenu("View") {
                Toggle("Show Menu Bar Widget", isOn: $showMenuBarWidget)
            }

            CommandMenu("Debug") {
                Button("Show Log Viewer") {
                    NSApp.sendAction(#selector(NSApplication.showLogWindow(_:)), to: nil, from: nil)
                }
                .keyboardShortcut("l", modifiers: [.command, .option])

                Divider()

                Button("Clear Logs") {
                    logManager.clear()
                }

                Toggle("Enable Logging", isOn: $logManager.isEnabled)

                Divider()

                Button("Export Logs...") {
                    exportLogs()
                }
            }
        }

        Settings {
            SettingsView()
        }

        // Log Viewer Window
        Window("Log Viewer", id: "log-viewer") {
            LogViewerWindow()
        }
        .defaultSize(width: 900, height: 500)
        .keyboardShortcut("l", modifiers: [.command, .option])

        // Menu Bar Widget
        MenuBarExtra(isInserted: $showMenuBarWidget) {
            MenuBarWidgetView()
                .environmentObject(dashboardViewModel)
        } label: {
            MenuBarLabel(viewModel: dashboardViewModel)
        }
        .menuBarExtraStyle(.window)
    }

    private func exportLogs() {
        let content = logManager.export()
        let panel = NSSavePanel()
        panel.nameFieldStringValue = "bcb-dashboard-logs-\(Date().ISO8601Format()).txt"
        panel.allowedContentTypes = [.plainText]

        if panel.runModal() == .OK, let url = panel.url {
            try? content.write(to: url, atomically: true, encoding: .utf8)
        }
    }
}

// Extension to handle log window opening
extension NSApplication {
    @objc func showLogWindow(_ sender: Any?) {
        // Open the log viewer window
        if let window = windows.first(where: { $0.title == "Log Viewer" }) {
            window.makeKeyAndOrderFront(nil)
        } else {
            // Use environment to open window
            NotificationCenter.default.post(name: NSNotification.Name("OpenLogViewer"), object: nil)
        }
    }
}

// MARK: - Menu Bar Label

struct MenuBarLabel: View {
    @ObservedObject var viewModel: DashboardViewModel

    var body: some View {
        HStack(spacing: 8) {
            // USD Value
            HStack(spacing: 2) {
                Text("$")
                    .font(.system(size: 10, weight: .medium))
                Text(viewModel.dollarQuote?.sellRate.toMenuBarString() ?? "--")
                    .font(.system(size: 11, weight: .semibold, design: .rounded))
            }

            // Separator
            Text("│")
                .font(.system(size: 9))
                .foregroundStyle(.secondary)

            // SELIC Value
            HStack(spacing: 1) {
                Text(String(format: "%.1f", viewModel.selicTarget?.numericValue ?? 0.0))
                    .font(.system(size: 11, weight: .semibold, design: .rounded))
                Text("%")
                    .font(.system(size: 9, weight: .medium))
            }
        }
    }
}

// MARK: - Menu Bar Widget View

struct MenuBarWidgetView: View {
    @EnvironmentObject var viewModel: DashboardViewModel
    @Environment(\.openWindow) private var openWindow

    var body: some View {
        VStack(spacing: 0) {
            // Header
            HStack {
                Image(systemName: "building.columns.fill")
                    .foregroundStyle(Color.bcbBlue)
                Text("BCB Dashboard")
                    .font(.headline)
                Spacer()
                if viewModel.isLoading {
                    ProgressView()
                        .controlSize(.small)
                }
            }
            .padding()
            .background(Color(NSColor.controlBackgroundColor))

            Divider()

            // Currency Section
            VStack(spacing: 12) {
                MenuBarRateRow(
                    icon: "dollarsign.circle.fill",
                    iconColor: .bcbGreen,
                    title: "Dólar (USD)",
                    value: viewModel.dollarQuote?.sellRate.toBRL() ?? "--",
                    subtitle: viewModel.dollarQuote?.formattedTime ?? "",
                    change: viewModel.dollarChange
                )

                MenuBarRateRow(
                    icon: "eurosign.circle.fill",
                    iconColor: .bcbBlue,
                    title: "Euro (EUR)",
                    value: viewModel.euroQuote?.sellRate.toBRL() ?? "--",
                    subtitle: viewModel.euroQuote?.formattedTime ?? "",
                    change: nil
                )

                Divider()
                    .padding(.vertical, 4)

                MenuBarRateRow(
                    icon: "percent",
                    iconColor: .bcbPurple,
                    title: "SELIC Meta",
                    value: String(format: "%.2f%%", viewModel.selicTarget?.numericValue ?? 0.0),
                    subtitle: viewModel.selicTarget?.date ?? "",
                    change: nil
                )

                MenuBarRateRow(
                    icon: "chart.line.uptrend.xyaxis",
                    iconColor: .bcbYellow,
                    title: "IPCA 12m",
                    value: String(format: "%.2f%%", viewModel.ipca12m?.numericValue ?? 0.0),
                    subtitle: viewModel.ipca12m?.date ?? "",
                    change: nil
                )

                if let selicExp = viewModel.focusStats.latestSelic {
                    Divider()
                        .padding(.vertical, 4)

                    MenuBarRateRow(
                        icon: "chart.bar.fill",
                        iconColor: .bcbPink,
                        title: "SELIC Esperada",
                        value: String(format: "%.2f%%", selicExp.median),
                        subtitle: selicExp.formattedMeeting,
                        change: nil
                    )
                }
            }
            .padding()

            Divider()

            // Footer with actions
            HStack {
                if let lastUpdate = viewModel.lastUpdate {
                    Text(lastUpdate.toRelativeString())
                        .font(.caption2)
                        .foregroundStyle(.secondary)
                }

                Spacer()

                Button {
                    Task {
                        await viewModel.refreshAll()
                    }
                } label: {
                    Image(systemName: "arrow.clockwise")
                        .font(.caption)
                }
                .buttonStyle(.borderless)
                .disabled(viewModel.isLoading)

                Divider()
                    .frame(height: 16)

                Button {
                    NSApplication.shared.activate(ignoringOtherApps: true)
                    if let window = NSApplication.shared.windows.first(where: { $0.title.contains("BCB") || $0.contentView != nil }) {
                        window.makeKeyAndOrderFront(nil)
                    }
                } label: {
                    Image(systemName: "macwindow")
                        .font(.caption)
                }
                .buttonStyle(.borderless)

                Divider()
                    .frame(height: 16)

                Button {
                    NSApplication.shared.terminate(nil)
                } label: {
                    Image(systemName: "power")
                        .font(.caption)
                }
                .buttonStyle(.borderless)
            }
            .padding(.horizontal)
            .padding(.vertical, 8)
            .background(Color(NSColor.controlBackgroundColor))
        }
        .frame(width: 280)
    }
}

// MARK: - Menu Bar Rate Row

struct MenuBarRateRow: View {
    let icon: String
    let iconColor: Color
    let title: String
    let value: String
    let subtitle: String
    let change: Double?

    var body: some View {
        HStack {
            Image(systemName: icon)
                .font(.title3)
                .foregroundStyle(iconColor)
                .frame(width: 24)

            VStack(alignment: .leading, spacing: 2) {
                Text(title)
                    .font(.caption)
                    .foregroundStyle(.secondary)
                Text(subtitle)
                    .font(.caption2)
                    .foregroundStyle(.tertiary)
            }

            Spacer()

            VStack(alignment: .trailing, spacing: 2) {
                Text(value)
                    .font(.system(.body, design: .rounded))
                    .fontWeight(.semibold)

                if let change = change, change != 0 {
                    HStack(spacing: 2) {
                        Image(systemName: change >= 0 ? "arrow.up.right" : "arrow.down.right")
                            .font(.caption2)
                        Text(String(format: "%.2f%%", abs(change)))
                            .font(.caption2)
                    }
                    .foregroundStyle(change >= 0 ? Color.bcbGreen : Color.bcbRed)
                }
            }
        }
    }
}

struct SettingsView: View {
    @AppStorage("refreshInterval") private var refreshInterval: Int = 5
    @AppStorage("autoLoadFocus") private var autoLoadFocus: Bool = true
    @AppStorage("autoLoadPIX") private var autoLoadPIX: Bool = true
    @AppStorage("autoLoadInstitutions") private var autoLoadInstitutions: Bool = false
    @AppStorage("cacheExpirationMinutes") private var cacheExpiration: Int = 5
    @AppStorage("showDecimalPlaces") private var decimalPlaces: Int = 2
    @AppStorage("compactMode") private var compactMode: Bool = false
    @AppStorage("showMenuBarWidget") private var showMenuBarWidget: Bool = true
    @StateObject private var notificationService = NotificationService.shared
    @State private var launchAtLogin = SMAppService.mainApp.status == .enabled

    var body: some View {
        TabView {
            // General Settings
            Form {
                Section("Inicialização") {
                    Toggle("Abrir ao iniciar sessão", isOn: $launchAtLogin)
                        .onChange(of: launchAtLogin) { _, newValue in
                            do {
                                if newValue {
                                    try SMAppService.mainApp.register()
                                } else {
                                    try SMAppService.mainApp.unregister()
                                }
                            } catch {
                                print("Failed to update login item: \(error)")
                                // Revert the toggle if it failed
                                launchAtLogin = SMAppService.mainApp.status == .enabled
                            }
                        }

                    Toggle("Mostrar na barra de menus", isOn: $showMenuBarWidget)

                    HStack {
                        Image(systemName: "info.circle")
                            .foregroundStyle(.secondary)
                        Text("O app continuará rodando na barra de menus mesmo com a janela fechada")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }

                Section("Atualização Automática") {
                    Picker("Intervalo de atualização", selection: $refreshInterval) {
                        Text("30 segundos").tag(0) // Special case: 0.5 min
                        Text("1 minuto").tag(1)
                        Text("2 minutos").tag(2)
                        Text("5 minutos").tag(5)
                        Text("10 minutos").tag(10)
                        Text("15 minutos").tag(15)
                        Text("30 minutos").tag(30)
                        Text("1 hora").tag(60)
                        Text("Desativado").tag(-1)
                    }
                    .pickerStyle(.menu)

                    if refreshInterval >= 0 {
                        HStack {
                            Image(systemName: "info.circle")
                                .foregroundStyle(.secondary)
                            Text(refreshInterval == 0 ? "Atualiza a cada 30 segundos" : "Atualiza a cada \(refreshInterval) minuto(s)")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }
                    }
                }

                Section("Cache") {
                    Picker("Expiração do cache", selection: $cacheExpiration) {
                        Text("1 minuto").tag(1)
                        Text("5 minutos").tag(5)
                        Text("15 minutos").tag(15)
                        Text("30 minutos").tag(30)
                        Text("1 hora").tag(60)
                    }
                    .pickerStyle(.menu)

                    HStack {
                        Image(systemName: "info.circle")
                            .foregroundStyle(.secondary)
                        Text("Dados em cache são reutilizados para evitar requisições repetidas")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }

                Section("Sobre") {
                    LabeledContent("Versão", value: "1.1.0")
                    LabeledContent("Fonte de dados", value: "Banco Central do Brasil")
                    LabeledContent("APIs utilizadas", value: "PTAX, SGS, SPI, Focus")

                    Link(destination: URL(string: "https://dadosabertos.bcb.gov.br")!) {
                        HStack {
                            Text("Portal de Dados Abertos BCB")
                            Spacer()
                            Image(systemName: "arrow.up.right.square")
                        }
                    }
                }
            }
            .formStyle(.grouped)
            .tabItem {
                Label("Geral", systemImage: "gear")
            }

            // Data Settings
            Form {
                Section("Carregamento Automático") {
                    Toggle("Carregar Focus (Expectativas) no início", isOn: $autoLoadFocus)
                    Toggle("Carregar PIX no início", isOn: $autoLoadPIX)
                    Toggle("Carregar Instituições no início", isOn: $autoLoadInstitutions)

                    HStack {
                        Image(systemName: "info.circle")
                            .foregroundStyle(.secondary)
                        Text("Desativar carregamento automático economiza dados e acelera o início")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }

                Section("Exibição") {
                    Picker("Casas decimais", selection: $decimalPlaces) {
                        Text("2 casas (R$ 5,37)").tag(2)
                        Text("3 casas (R$ 5,370)").tag(3)
                        Text("4 casas (R$ 5,3700)").tag(4)
                    }
                    .pickerStyle(.menu)

                    Toggle("Modo compacto", isOn: $compactMode)

                    HStack {
                        Image(systemName: "info.circle")
                            .foregroundStyle(.secondary)
                        Text("Modo compacto reduz espaçamentos e tamanho de fontes")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }

                Section("Dados") {
                    HStack {
                        VStack(alignment: .leading, spacing: 4) {
                            Text("APIs do Banco Central")
                                .font(.subheadline)
                            Text("Dados atualizados em tempo real")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }

                        Spacer()

                        Circle()
                            .fill(.green)
                            .frame(width: 8, height: 8)
                        Text("Online")
                            .font(.caption)
                            .foregroundStyle(.secondary)
                    }
                }
            }
            .formStyle(.grouped)
            .tabItem {
                Label("Dados", systemImage: "chart.bar.doc.horizontal")
            }

            // Notification Settings
            Form {
                Section("Notificações") {
                    Toggle("Ativar notificações", isOn: $notificationService.notificationsEnabled)
                        .onChange(of: notificationService.notificationsEnabled) { _, _ in
                            notificationService.saveSettings()
                        }

                    if !notificationService.isAuthorized {
                        HStack {
                            Image(systemName: "exclamationmark.triangle.fill")
                                .foregroundStyle(.yellow)
                            Text("Permissão de notificações não concedida")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                        }

                        Button("Solicitar Permissão") {
                            Task {
                                await notificationService.requestAuthorization()
                            }
                        }
                    }
                }

                Section("Alertas de Câmbio") {
                    Toggle("Notificar qualquer mudança", isOn: $notificationService.notifyOnAnyChange)
                        .onChange(of: notificationService.notifyOnAnyChange) { _, _ in
                            notificationService.saveSettings()
                        }

                    if !notificationService.notifyOnAnyChange {
                        HStack {
                            Text("Variação mínima:")
                            Slider(value: $notificationService.dollarChangeThreshold, in: 0.1...5.0, step: 0.1)
                                .onChange(of: notificationService.dollarChangeThreshold) { _, _ in
                                    notificationService.saveSettings()
                                }
                            Text(String(format: "%.1f%%", notificationService.dollarChangeThreshold))
                                .monospacedDigit()
                                .frame(width: 50)
                        }
                    }
                }

                Section("Teste") {
                    Button("Enviar Notificação de Teste") {
                        notificationService.sendTestNotification()
                    }
                    .disabled(!notificationService.isAuthorized || !notificationService.notificationsEnabled)

                    Button("Limpar Todas as Notificações") {
                        notificationService.clearAllNotifications()
                    }
                }
            }
            .formStyle(.grouped)
            .tabItem {
                Label("Notificações", systemImage: "bell")
            }

            // Keyboard Shortcuts
            Form {
                Section("Atalhos de Teclado") {
                    KeyboardShortcutRow(keys: "⌘ R", description: "Atualizar todos os dados")
                    KeyboardShortcutRow(keys: "⌘ ⇧ K", description: "Limpar cache")
                    KeyboardShortcutRow(keys: "⌘ ,", description: "Abrir configurações")
                    KeyboardShortcutRow(keys: "⌘ 1-6", description: "Navegar entre abas")
                }

                Section("Navegação") {
                    KeyboardShortcutRow(keys: "⌘ [", description: "Voltar")
                    KeyboardShortcutRow(keys: "⌘ ]", description: "Avançar")
                    KeyboardShortcutRow(keys: "⌘ W", description: "Fechar janela")
                }
            }
            .formStyle(.grouped)
            .tabItem {
                Label("Atalhos", systemImage: "keyboard")
            }
        }
        .frame(width: 500, height: 400)
    }
}

struct KeyboardShortcutRow: View {
    let keys: String
    let description: String

    var body: some View {
        HStack {
            Text(keys)
                .font(.system(.body, design: .monospaced))
                .padding(.horizontal, 8)
                .padding(.vertical, 4)
                .background(Color(NSColor.controlBackgroundColor))
                .clipShape(RoundedRectangle(cornerRadius: 6))

            Text(description)
                .foregroundStyle(.secondary)

            Spacer()
        }
    }
}


     1→// swift-tools-version: 5.9
     2→import PackageDescription
     3→
     4→let package = Package(
     5→    name: "BCBDashboard",
     6→    platforms: [
     7→        .macOS(.v14)
     8→    ],
     9→    products: [
    10→        .executable(name: "BCBDashboard", targets: ["BCBDashboard"])
    11→    ],
    12→    targets: [
    13→        .executableTarget(
    14→            name: "BCBDashboard",
    15→            path: ".",
    16→            exclude: ["Package.swift", "CLAUDE.md"],
    17→            sources: [
    18→                "BCBDashboardApp.swift",
    19→                "Models/Currency.swift",
    20→                "Models/TimeSeries.swift",
    21→                "Models/Institution.swift",
    22→                "Services/BCBAPIClient.swift",
    23→                "Services/NotificationService.swift",
    24→                "ViewModels/DashboardViewModel.swift",
    25→                "Views/ContentView.swift",
    26→                "Views/Dashboard/DashboardView.swift",
    27→                "Views/Currency/CurrencyListView.swift",
    28→                "Views/Rates/RatesView.swift",
    29→                "Views/Institutions/InstitutionsView.swift",
    30→                "Views/PIX/PIXStatsView.swift",
    31→                "Views/Expectations/ExpectationsView.swift",
    32→                "Extensions/Extensions.swift"
    33→            ]
    34→        )
    35→    ]
    36→)
    37→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

// swift-tools-version: 5.9
import PackageDescription

let package = Package(
    name: "BCBDashboard",
    platforms: [
        .macOS(.v14)
    ],
    products: [
        .executable(name: "BCBDashboard", targets: ["BCBDashboard"])
    ],
    targets: [
        .executableTarget(
            name: "BCBDashboard",
            path: ".",
            exclude: ["Package.swift", "CLAUDE.md"],
            sources: [
                "BCBDashboardApp.swift",
                "Models/Currency.swift",
                "Models/TimeSeries.swift",
                "Models/Institution.swift",
                "Services/BCBAPIClient.swift",
                "Services/NotificationService.swift",
                "ViewModels/DashboardViewModel.swift",
                "Views/ContentView.swift",
                "Views/Dashboard/DashboardView.swift",
                "Views/Currency/CurrencyListView.swift",
                "Views/Rates/RatesView.swift",
                "Views/Institutions/InstitutionsView.swift",
                "Views/PIX/PIXStatsView.swift",
                "Views/Expectations/ExpectationsView.swift",
                "Extensions/Extensions.swift"
            ]
        )
    ]
)


Summary: 96Blocks iOS Critical Fixes & Phase 2 Corrections


Summary: 96Blocks iOS: Critical & High Priority Bug Fixes


Summary: iOS App Bug Fixes & Optimization Analysis


Vou ler mais arquivos para ter uma visão completa do projeto.


Summary: 96Blocks iOS Bug Fix & Optimization Complete


Summary: 96Blocks iOS App Crash Fix


Summary: 96Blocks iOS App Crash Fix & Debugging


     1→import Foundation
     2→
     3→// MARK: - PTAX API Response Models
     4→
     5→struct PTAXResponse<T: Codable>: Codable {
     6→    let value: [T]
     7→
     8→    enum CodingKeys: String, CodingKey {
     9→        case value
    10→    }
    11→}
    12→
    13→struct CurrencyInfo: Codable, Identifiable, Hashable {
    14→    var id: String { symbol }
    15→    let symbol: String
    16→    let formattedName: String
    17→    let currencyType: String
    18→
    19→    enum CodingKeys: String, CodingKey {
    20→        case symbol = "simbolo"
    21→        case formattedName = "nomeFormatado"
    22→        case currencyType = "tipoMoeda"
    23→    }
    24→
    25→    var flag: String {
    26→        switch symbol {
    27→        case "USD": return "🇺🇸"
    28→        case "EUR": return "🇪🇺"
    29→        case "GBP": return "🇬🇧"
    30→        case "JPY": return "🇯🇵"
    31→        case "CHF": return "🇨🇭"
    32→        case "CAD": return "🇨🇦"
    33→        case "AUD": return "🇦🇺"
    34→        case "CNY": return "🇨🇳"
    35→        case "ARS": return "🇦🇷"
    36→        case "MXN": return "🇲🇽"
    37→        default: return "💱"
    38→        }
    39→    }
    40→}
    41→
    42→struct DollarQuote: Codable, Identifiable {
    43→    var id: String { dataHoraCotacao }
    44→    let buyRate: Double
    45→    let sellRate: Double
    46→    let dataHoraCotacao: String
    47→
    48→    enum CodingKeys: String, CodingKey {
    49→        case buyRate = "cotacaoCompra"
    50→        case sellRate = "cotacaoVenda"
    51→        case dataHoraCotacao
    52→    }
    53→
    54→    var dateTime: Date? {
    55→        let formatter = DateFormatter()
    56→        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
    57→        return formatter.date(from: dataHoraCotacao)
    58→    }
    59→
    60→    var formattedDate: String {
    61→        guard let date = dateTime else { return dataHoraCotacao }
    62→        let formatter = DateFormatter()
    63→        formatter.dateFormat = "dd/MM/yyyy"
    64→        return formatter.string(from: date)
    65→    }
    66→
    67→    var formattedTime: String {
    68→        guard let date = dateTime else { return "--" }
    69→        let formatter = DateFormatter()
    70→        formatter.dateFormat = "HH:mm:ss"
    71→        return formatter.string(from: date)
    72→    }
    73→}
    74→
    75→struct CurrencyQuote: Codable, Identifiable {
    76→    var id: String { "\(dataHoraCotacao)-\(tipoBoletim ?? "default")" }
    77→    let parityBuy: Double?
    78→    let paritySell: Double?
    79→    let buyRate: Double
    80→    let sellRate: Double
    81→    let dataHoraCotacao: String
    82→    let bulletinType: String?
    83→
    84→    enum CodingKeys: String, CodingKey {
    85→        case parityBuy = "paridadeCompra"
    86→        case paritySell = "paridadeVenda"
    87→        case buyRate = "cotacaoCompra"
    88→        case sellRate = "cotacaoVenda"
    89→        case dataHoraCotacao
    90→        case bulletinType = "tipoBoletim"
    91→    }
    92→
    93→    var tipoBoletim: String? { bulletinType }
    94→
    95→    var dateTime: Date? {
    96→        let formatter = DateFormatter()
    97→        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
    98→        if let date = formatter.date(from: dataHoraCotacao) {
    99→            return date
   100→        }
   101→        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SS"
   102→        return formatter.date(from: dataHoraCotacao)
   103→    }
   104→
   105→    var formattedDate: String {
   106→        guard let date = dateTime else { return dataHoraCotacao }
   107→        let formatter = DateFormatter()
   108→        formatter.dateFormat = "dd/MM/yyyy"
   109→        return formatter.string(from: date)
   110→    }
   111→
   112→    var formattedTime: String {
   113→        guard let date = dateTime else { return "--" }
   114→        let formatter = DateFormatter()
   115→        formatter.dateFormat = "HH:mm:ss"
   116→        return formatter.string(from: date)
   117→    }
   118→
   119→    var isPTAXClosing: Bool {
   120→        bulletinType?.lowercased().contains("fechamento") == true ||
   121→        bulletinType?.lowercased().contains("ptax") == true
   122→    }
   123→}
   124→
   125→// MARK: - Display Model
   126→
   127→struct CurrencyDisplayData: Identifiable {
   128→    let id = UUID()
   129→    let symbol: String
   130→    let name: String
   131→    let flag: String
   132→    let buyRate: Double
   133→    let sellRate: Double
   134→    let change: Double
   135→    let changePercent: Double
   136→    let lastUpdate: Date?
   137→    let history: [DollarQuote]
   138→
   139→    var isPositive: Bool { change >= 0 }
   140→
   141→    var formattedBuyRate: String {
   142→        buyRate.toBRL()
   143→    }
   144→
   145→    var formattedSellRate: String {
   146→        sellRate.toBRL()
   147→    }
   148→
   149→    var formattedChange: String {
   150→        let prefix = change >= 0 ? "+" : ""
   151→        return "\(prefix)\(String(format: "%.4f", change))"
   152→    }
   153→
   154→    var formattedChangePercent: String {
   155→        let prefix = changePercent >= 0 ? "↑" : "↓"
   156→        return "\(prefix) \(String(format: "%.2f", abs(changePercent)))%"
   157→    }
   158→}
   159→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation

// MARK: - PTAX API Response Models

struct PTAXResponse<T: Codable>: Codable {
    let value: [T]

    enum CodingKeys: String, CodingKey {
        case value
    }
}

struct CurrencyInfo: Codable, Identifiable, Hashable {
    var id: String { symbol }
    let symbol: String
    let formattedName: String
    let currencyType: String

    enum CodingKeys: String, CodingKey {
        case symbol = "simbolo"
        case formattedName = "nomeFormatado"
        case currencyType = "tipoMoeda"
    }

    var flag: String {
        switch symbol {
        case "USD": return "🇺🇸"
        case "EUR": return "🇪🇺"
        case "GBP": return "🇬🇧"
        case "JPY": return "🇯🇵"
        case "CHF": return "🇨🇭"
        case "CAD": return "🇨🇦"
        case "AUD": return "🇦🇺"
        case "CNY": return "🇨🇳"
        case "ARS": return "🇦🇷"
        case "MXN": return "🇲🇽"
        default: return "💱"
        }
    }
}

struct DollarQuote: Codable, Identifiable {
    var id: String { dataHoraCotacao }
    let buyRate: Double
    let sellRate: Double
    let dataHoraCotacao: String

    enum CodingKeys: String, CodingKey {
        case buyRate = "cotacaoCompra"
        case sellRate = "cotacaoVenda"
        case dataHoraCotacao
    }

    var dateTime: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
        return formatter.date(from: dataHoraCotacao)
    }

    var formattedDate: String {
        guard let date = dateTime else { return dataHoraCotacao }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }

    var formattedTime: String {
        guard let date = dateTime else { return "--" }
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        return formatter.string(from: date)
    }
}

struct CurrencyQuote: Codable, Identifiable {
    var id: String { "\(dataHoraCotacao)-\(tipoBoletim ?? "default")" }
    let parityBuy: Double?
    let paritySell: Double?
    let buyRate: Double
    let sellRate: Double
    let dataHoraCotacao: String
    let bulletinType: String?

    enum CodingKeys: String, CodingKey {
        case parityBuy = "paridadeCompra"
        case paritySell = "paridadeVenda"
        case buyRate = "cotacaoCompra"
        case sellRate = "cotacaoVenda"
        case dataHoraCotacao
        case bulletinType = "tipoBoletim"
    }

    var tipoBoletim: String? { bulletinType }

    var dateTime: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"
        if let date = formatter.date(from: dataHoraCotacao) {
            return date
        }
        formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SS"
        return formatter.date(from: dataHoraCotacao)
    }

    var formattedDate: String {
        guard let date = dateTime else { return dataHoraCotacao }
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.string(from: date)
    }

    var formattedTime: String {
        guard let date = dateTime else { return "--" }
        let formatter = DateFormatter()
        formatter.dateFormat = "HH:mm:ss"
        return formatter.string(from: date)
    }

    var isPTAXClosing: Bool {
        bulletinType?.lowercased().contains("fechamento") == true ||
        bulletinType?.lowercased().contains("ptax") == true
    }
}

// MARK: - Display Model

struct CurrencyDisplayData: Identifiable {
    let id = UUID()
    let symbol: String
    let name: String
    let flag: String
    let buyRate: Double
    let sellRate: Double
    let change: Double
    let changePercent: Double
    let lastUpdate: Date?
    let history: [DollarQuote]

    var isPositive: Bool { change >= 0 }

    var formattedBuyRate: String {
        buyRate.toBRL()
    }

    var formattedSellRate: String {
        sellRate.toBRL()
    }

    var formattedChange: String {
        let prefix = change >= 0 ? "+" : ""
        return "\(prefix)\(String(format: "%.4f", change))"
    }

    var formattedChangePercent: String {
        let prefix = changePercent >= 0 ? "↑" : "↓"
        return "\(prefix) \(String(format: "%.2f", abs(changePercent)))%"
    }
}


     1→import Foundation
     2→
     3→// MARK: - SGS API Models
     4→
     5→struct TimeSeriesValue: Codable, Identifiable {
     6→    var id: String { date }
     7→    let date: String
     8→    let value: String
     9→
    10→    enum CodingKeys: String, CodingKey {
    11→        case date = "data"
    12→        case value = "valor"
    13→    }
    14→
    15→    var numericValue: Double {
    16→        Double(value) ?? 0
    17→    }
    18→
    19→    var parsedDate: Date? {
    20→        let formatter = DateFormatter()
    21→        formatter.dateFormat = "dd/MM/yyyy"
    22→        return formatter.date(from: date)
    23→    }
    24→}
    25→
    26→// MARK: - Series Codes
    27→
    28→enum SeriesCode: Int, CaseIterable, Identifiable {
    29→    case selicDaily = 11
    30→    case selicTarget = 432
    31→    case ipcaMonthly = 433
    32→    case ipca12m = 13522
    33→    case cdi = 4389
    34→    case dollarBuy = 1
    35→    case gdpMonthly = 21619
    36→    case publicDebt = 27574
    37→    case unemployment = 24369
    38→    case industrialProduction = 21859
    39→
    40→    var id: Int { rawValue }
    41→
    42→    var name: String {
    43→        switch self {
    44→        case .selicDaily: return "SELIC Diária"
    45→        case .selicTarget: return "SELIC Meta"
    46→        case .ipcaMonthly: return "IPCA Mensal"
    47→        case .ipca12m: return "IPCA 12 Meses"
    48→        case .cdi: return "CDI"
    49→        case .dollarBuy: return "Dólar (Compra)"
    50→        case .gdpMonthly: return "PIB Mensal"
    51→        case .publicDebt: return "Dívida Pública"
    52→        case .unemployment: return "Desemprego"
    53→        case .industrialProduction: return "Produção Industrial"
    54→        }
    55→    }
    56→
    57→    var shortName: String {
    58→        switch self {
    59→        case .selicDaily: return "SELIC"
    60→        case .selicTarget: return "SELIC"
    61→        case .ipcaMonthly: return "IPCA"
    62→        case .ipca12m: return "IPCA"
    63→        case .cdi: return "CDI"
    64→        case .dollarBuy: return "USD"
    65→        case .gdpMonthly: return "PIB"
    66→        case .publicDebt: return "Dívida"
    67→        case .unemployment: return "Desemp."
    68→        case .industrialProduction: return "Ind."
    69→        }
    70→    }
    71→
    72→    var unit: String {
    73→        switch self {
    74→        case .selicDaily: return "% a.d."
    75→        case .selicTarget, .cdi: return "% a.a."
    76→        case .ipcaMonthly: return "%"
    77→        case .ipca12m: return "% 12m"
    78→        case .dollarBuy: return "R$"
    79→        case .gdpMonthly: return "R$ bi"
    80→        case .publicDebt: return "R$ bi"
    81→        case .unemployment: return "%"
    82→        case .industrialProduction: return "índice"
    83→        }
    84→    }
    85→
    86→    var icon: String {
    87→        switch self {
    88→        case .selicDaily, .selicTarget: return "percent"
    89→        case .ipcaMonthly, .ipca12m: return "chart.line.uptrend.xyaxis"
    90→        case .cdi: return "banknote"
    91→        case .dollarBuy: return "dollarsign.circle"
    92→        case .gdpMonthly: return "chart.bar"
    93→        case .publicDebt: return "building.columns"
    94→        case .unemployment: return "person.crop.circle.badge.minus"
    95→        case .industrialProduction: return "gearshape.2"
    96→        }
    97→    }
    98→
    99→    var color: String {
   100→        switch self {
   101→        case .selicDaily, .selicTarget: return "#8B5CF6"
   102→        case .ipcaMonthly, .ipca12m: return "#F59E0B"
   103→        case .cdi: return "#EC4899"
   104→        case .dollarBuy: return "#10B981"
   105→        case .gdpMonthly: return "#3B82F6"
   106→        case .publicDebt: return "#EF4444"
   107→        case .unemployment: return "#6366F1"
   108→        case .industrialProduction: return "#14B8A6"
   109→        }
   110→    }
   111→
   112→    var category: SeriesCategory {
   113→        switch self {
   114→        case .selicDaily, .selicTarget, .cdi:
   115→            return .interestRates
   116→        case .ipcaMonthly, .ipca12m:
   117→            return .inflation
   118→        case .dollarBuy:
   119→            return .currency
   120→        case .gdpMonthly, .industrialProduction:
   121→            return .economy
   122→        case .publicDebt, .unemployment:
   123→            return .fiscal
   124→        }
   125→    }
   126→}
   127→
   128→enum SeriesCategory: String, CaseIterable {
   129→    case interestRates = "Taxas de Juros"
   130→    case inflation = "Inflação"
   131→    case currency = "Câmbio"
   132→    case economy = "Economia"
   133→    case fiscal = "Fiscal"
   134→}
   135→
   136→// MARK: - Display Model
   137→
   138→struct RateDisplayData: Identifiable {
   139→    let id = UUID()
   140→    let series: SeriesCode
   141→    let currentValue: Double
   142→    let previousValue: Double?
   143→    let lastUpdate: String
   144→    let history: [TimeSeriesValue]
   145→
   146→    var change: Double {
   147→        guard let previous = previousValue else { return 0 }
   148→        return currentValue - previous
   149→    }
   150→
   151→    var changePercent: Double {
   152→        guard let previous = previousValue, previous != 0 else { return 0 }
   153→        return ((currentValue - previous) / previous) * 100
   154→    }
   155→
   156→    var isPositive: Bool {
   157→        change >= 0
   158→    }
   159→
   160→    var formattedValue: String {
   161→        switch series {
   162→        case .selicDaily:
   163→            return String(format: "%.6f%%", currentValue)
   164→        case .selicTarget, .cdi, .ipcaMonthly, .ipca12m, .unemployment:
   165→            return String(format: "%.2f%%", currentValue)
   166→        case .dollarBuy:
   167→            return currentValue.toBRL()
   168→        case .gdpMonthly, .publicDebt:
   169→            return String(format: "%.1f bi", currentValue)
   170→        case .industrialProduction:
   171→            return String(format: "%.2f", currentValue)
   172→        }
   173→    }
   174→
   175→    var formattedChange: String {
   176→        let prefix = change >= 0 ? "↑" : "↓"
   177→        return "\(prefix) \(String(format: "%.2f", abs(changePercent)))%"
   178→    }
   179→}
   180→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>

import Foundation

// MARK: - SGS API Models

struct TimeSeriesValue: Codable, Identifiable {
    var id: String { date }
    let date: String
    let value: String

    enum CodingKeys: String, CodingKey {
        case date = "data"
        case value = "valor"
    }

    var numericValue: Double {
        Double(value) ?? 0
    }

    var parsedDate: Date? {
        let formatter = DateFormatter()
        formatter.dateFormat = "dd/MM/yyyy"
        return formatter.date(from: date)
    }
}

// MARK: - Series Codes

enum SeriesCode: Int, CaseIterable, Identifiable {
    case selicDaily = 11
    case selicTarget = 432
    case ipcaMonthly = 433
    case ipca12m = 13522
    case cdi = 4389
    case dollarBuy = 1
    case gdpMonthly = 21619
    case publicDebt = 27574
    case unemployment = 24369
    case industrialProduction = 21859

    var id: Int { rawValue }

    var name: String {
        switch self {
        case .selicDaily: return "SELIC Diária"
        case .selicTarget: return "SELIC Meta"
        case .ipcaMonthly: return "IPCA Mensal"
        case .ipca12m: return "IPCA 12 Meses"
        case .cdi: return "CDI"
        case .dollarBuy: return "Dólar (Compra)"
        case .gdpMonthly: return "PIB Mensal"
        case .publicDebt: return "Dívida Pública"
        case .unemployment: return "Desemprego"
        case .industrialProduction: return "Produção Industrial"
        }
    }

    var shortName: String {
        switch self {
        case .selicDaily: return "SELIC"
        case .selicTarget: return "SELIC"
        case .ipcaMonthly: return "IPCA"
        case .ipca12m: return "IPCA"
        case .cdi: return "CDI"
        case .dollarBuy: return "USD"
        case .gdpMonthly: return "PIB"
        case .publicDebt: return "Dívida"
        case .unemployment: return "Desemp."
        case .industrialProduction: return "Ind."
        }
    }

    var unit: String {
        switch self {
        case .selicDaily: return "% a.d."
        case .selicTarget, .cdi: return "% a.a."
        case .ipcaMonthly: return "%"
        case .ipca12m: return "% 12m"
        case .dollarBuy: return "R$"
        case .gdpMonthly: return "R$ bi"
        case .publicDebt: return "R$ bi"
        case .unemployment: return "%"
        case .industrialProduction: return "índice"
        }
    }

    var icon: String {
        switch self {
        case .selicDaily, .selicTarget: return "percent"
        case .ipcaMonthly, .ipca12m: return "chart.line.uptrend.xyaxis"
        case .cdi: return "banknote"
        case .dollarBuy: return "dollarsign.circle"
        case .gdpMonthly: return "chart.bar"
        case .publicDebt: return "building.columns"
        case .unemployment: return "person.crop.circle.badge.minus"
        case .industrialProduction: return "gearshape.2"
        }
    }

    var color: String {
        switch self {
        case .selicDaily, .selicTarget: return "#8B5CF6"
        case .ipcaMonthly, .ipca12m: return "#F59E0B"
        case .cdi: return "#EC4899"
        case .dollarBuy: return "#10B981"
        case .gdpMonthly: return "#3B82F6"
        case .publicDebt: return "#EF4444"
        case .unemployment: return "#6366F1"
        case .industrialProduction: return "#14B8A6"
        }
    }

    var category: SeriesCategory {
        switch self {
        case .selicDaily, .selicTarget, .cdi:
            return .interestRates
        case .ipcaMonthly, .ipca12m:
            return .inflation
        case .dollarBuy:
            return .currency
        case .gdpMonthly, .industrialProduction:
            return .economy
        case .publicDebt, .unemployment:
            return .fiscal
        }
    }
}

enum SeriesCategory: String, CaseIterable {
    case interestRates = "Taxas de Juros"
    case inflation = "Inflação"
    case currency = "Câmbio"
    case economy = "Economia"
    case fiscal = "Fiscal"
}

// MARK: - Display Model

struct RateDisplayData: Identifiable {
    let id = UUID()
    let series: SeriesCode
    let currentValue: Double
    let previousValue: Double?
    let lastUpdate: String
    let history: [TimeSeriesValue]

    var change: Double {
        guard let previous = previousValue else { return 0 }
        return currentValue - previous
    }

    var changePercent: Double {
        guard let previous = previousValue, previous != 0 else { return 0 }
        return ((currentValue - previous) / previous) * 100
    }

    var isPositive: Bool {
        change >= 0
    }

    var formattedValue: String {
        switch series {
        case .selicDaily:
            return String(format: "%.6f%%", currentValue)
        case .selicTarget, .cdi, .ipcaMonthly, .ipca12m, .unemployment:
            return String(format: "%.2f%%", currentValue)
        case .dollarBuy:
            return currentValue.toBRL()
        case .gdpMonthly, .publicDebt:
            return String(format: "%.1f bi", currentValue)
        case .industrialProduction:
            return String(format: "%.2f", currentValue)
        }
    }

    var formattedChange: String {
        let prefix = change >= 0 ? "↑" : "↓"
        return "\(prefix) \(String(format: "%.2f", abs(changePercent)))%"
    }
}