# DoncMobile App -- Guia de Arquitetura **Versao:** v1.0 -- Mar 2026 **Stack:** .NET MAUI + CommunityToolkit.Mvvm + EF Core + SQLite + Refit **Idioma do codigo:** Portugues (BR) ## 01 -- Arquitetura MVVM O app segue MVVM estrito com cinco camadas bem definidas: | Camada | Descricao | Pasta | |--------|-----------|-------| | Views | Paginas XAML -- so UI, sem logica de negocio | Views/ | | ViewModels | Logica de apresentacao -- herdam de BaseViewModel | ViewModels/ | | Services | Regras de negocio, sincronizacao, autenticacao | Services/ | | Repositories | Acesso ao SQLite offline via EF Core | Data/Repositorios/ | | API Clients | Clientes REST tipados com Refit | Services/Api/ | **Tecnologias principais:** .NET MAUI, CommunityToolkit.Mvvm (source generators), EF Core + SQLite (offline-first), Refit (clientes REST tipados), ILogger (logging estruturado) **Fluxo de dados:** 1. **View dispara comando** -- O usuario interage com a UI. A View chama um RelayCommand na ViewModel via binding. 2. **ViewModel executa via ExecutarAsync** -- O comando chama ExecutarAsync(), que gerencia IsLoading, captura excecoes e exibe erros automaticamente. 3. **Service / Repository executa a logica** -- A ViewModel delega para o servico ou repositorio. Nenhuma logica de banco ou HTTP fica na ViewModel. 4. **View atualiza via data binding** -- As propriedades [ObservableProperty] notificam a View automaticamente via INotifyPropertyChanged. ## 02 -- Estrutura de Pastas Cada pasta tem uma responsabilidade unica e bem definida. Nunca coloque codigo em uma pasta errada. ``` src/DoncMobile.App/ ├── Configuration/ -- ApiConfiguration: URL base, timeouts ├── Controls/ -- Controles visuais customizados ├── Converters/ -- Value converters para XAML ├── Core/ │ ├── Dialog/ -- IDialogService + DialogService │ ├── Navigation/ -- INavegacaoService + NavegacaoService │ └── ViewModels/ -- BaseViewModel (toda ViewModel herda daqui) ├── Data/ │ ├── Entities/ -- Entidades do SQLite (OfflineDbContext) │ └── Repositorios/ -- Padrao Repository para acesso offline ├── Extensions/ -- Metodos de extensao para DI em MauiProgram │ ├── BancoExtensions.cs │ ├── HttpClientExtensions.cs │ ├── ServicosExtensions.cs │ ├── ViewModelsExtensions.cs │ └── ViewsExtensions.cs ├── Models/ │ ├── Api/ -- DTOs de request/response da API REST │ └── Dominio/ -- Modelos de dominio e UI ├── Services/ -- Servicos de negocio e infraestrutura │ ├── Api/ -- Interfaces Refit │ ├── Authentication/ │ ├── Sync/ -- SyncService, DataSyncService │ └── Storage/ -- IStorageService (SecureStorage) ├── ViewModels/ -- Uma pasta por feature grande │ ├── ExecucaoServico/ -- ViewModel dividida em parciais │ ├── Rotas/ │ └── LoginViewModel.cs └── Views/ -- Pages XAML, uma pasta por feature ``` > **ALERTA:** Nunca registre DI direto em MauiProgram.cs. Todo registro fica nas classes de Extensions/. O MauiProgram.cs apenas chama os metodos de extensao. ## 03 -- BaseViewModel & ExecutarAsync Toda ViewModel herda de BaseViewModel. Ela fornece as propriedades de estado, as dependencias padrao e o metodo ExecutarAsync que centraliza tratamento de loading, erro e log. **O que BaseViewModel fornece:** | Membro | Tipo | Descricao | |--------|------|-----------| | IsLoading | bool | Verdadeiro enquanto ExecutarAsync esta rodando. Bind ao ActivityIndicator. | | MensagemStatus | string | Texto de feedback para exibir ao usuario durante operacoes longas. | | HasError | bool | Verdadeiro quando a ultima execucao resultou em excecao. | | ErrorMessage | string | Mensagem do erro capturado (traduzida para portugues). | | Logger | ILogger | Logger estruturado injetado. Use Logger.LogInformation, LogWarning, LogError. | | Navegacao | INavegacaoService | Servico de navegacao. Nunca use Shell.Current.GoToAsync() diretamente. | | Dialog | IDialogService | Servico de dialogos. Nunca use DisplayAlert() diretamente. | | ExecutarAsync() | metodo | Envolve a logica do comando com loading, catch e log automaticos. | | InicializarAsync() | metodo | Ciclo de vida -- chamado no OnAppearing da Page. | | DesativarAsync() | metodo | Ciclo de vida -- chamado no OnDisappearing da Page. | **Como criar uma ViewModel:** ```csharp using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Input; using DoncMobile.App.Core.Dialog; using DoncMobile.App.Core.Navigation; using DoncMobile.App.Core.ViewModels; using Microsoft.Extensions.Logging; namespace DoncMobile.App.ViewModels; public partial class MinhaViewModel : BaseViewModel { private readonly IAlgumServico _servico; [ObservableProperty] private string titulo = string.Empty; public MinhaViewModel( IAlgumServico servico, ILogger logger, INavegacaoService navegacao, IDialogService dialog) : base(logger, navegacao, dialog) { _servico = servico; } public override Task InicializarAsync() => ExecutarAsync(async () => { var dados = await _servico.BuscarDadosAsync(); Titulo = dados.Titulo; }, "InicializarMinhaPage"); [RelayCommand] private Task SalvarAsync() => ExecutarAsync(async () => { await _servico.SalvarAsync(Titulo); await Dialog.InformarAsync("Sucesso", "Dados salvos com sucesso!"); await Navegacao.VoltarAsync(); }, "Salvar"); } ``` **Regras do ExecutarAsync:** - ERRADO: try/catch manual, IsLoading manual, Shell.Current.DisplayAlert direto. - CORRETO: ExecutarAsync cuida de tudo: IsLoading, catch, log e Dialog.MostrarErroAsync sao tratados automaticamente. > **ALERTA:** Nunca declare IsLoading, HasError ou ErrorMessage na ViewModel. Essas propriedades ja existem em BaseViewModel. Redeclara-las causa conflito de nomes com os source generators do CommunityToolkit.Mvvm. ## 04 -- Navegacao Toda navegacao passa pelo INavegacaoService. Isso permite testar, mockar e alterar rotas em um unico lugar. - NUNCA: `Shell.Current.GoToAsync("//RotasPage")` - SEMPRE: `Navegacao.IrParaRotasAsync()` **Rotas disponiveis no INavegacaoService:** | Metodo | Destino | Parametros | |--------|---------|------------| | IrParaLoginAsync() | Tela de login | -- | | IrParaSetupAsync() | Configuracao da empresa | -- | | IrParaSincronizacaoAsync() | Tela de sync | -- | | IrParaRotasAsync() | Lista de rotas (tab principal) | -- | | IrParaDetalhesRotaAsync() | Detalhes da rota | rotaId, pedidoId | | IrParaExecucaoServicoAsync() | Execucao do servico | rotaId, pedidoId | | IrParaRelatorioProblemaAsync() | Relatorio de problema | rotaId, pedidoId, codigoRota, codigoPedido | | IrParaWebViewAsync() | WebView embutido | url, titulo | | IrParaBuscaPecasAsync() | Busca de pecas | IList | | VoltarAsync() | Pagina anterior | -- | **Como adicionar uma nova rota:** 1. Declarar na interface -- Core/Navigation/INavegacaoService.cs 2. Implementar no servico -- Core/Navigation/NavegacaoService.cs 3. Registrar a rota no Shell -- AppShell.xaml.cs com Routing.RegisterRoute() > **ALERTA:** Nomes de classes, metodos e propriedades em portugues. Mas as strings de rota Shell (legado) ficam em ingles: "route-details", "//RotasPage". Nao altere rotas existentes. ## 05 -- Dialogos Todos os alertas, confirmacoes e selecoes passam pelo IDialogService. | Metodo | Quando usar | Retorno | |--------|-------------|---------| | InformarAsync(titulo, msg) | Feedback ao usuario -- sucesso, aviso | Task | | MostrarErroAsync(msg) | Exibir erro. Ja chamado automaticamente pelo ExecutarAsync. | Task | | ConfirmarAsync(titulo, msg) | Confirmacoes destrutivas (deletar, cancelar) | Task\ | | SelecionarOpcaoAsync(titulo, cancelar, opcoes) | ActionSheet com lista de opcoes | Task\ | **Proibido nas ViewModels:** Shell.Current.DisplayAlert(), Application.Current.MainPage.DisplayAlert(), Page.DisplayAlert() diretamente. ## 06 -- Repository Pattern ViewModels nunca injetam OfflineDbContext diretamente. Todo acesso ao banco SQLite passa por repositorios em Data/Repositorios/. - ERRADO: ViewModel injeta OfflineDbContext e faz queries diretas. - CORRETO: ViewModel injeta IRepositorioPedidoRota e chama repositorio.ObterPorIdAsync(id). **Repositorios existentes:** | Interface | Responsabilidade | |-----------|-----------------| | IRepositorioPedidoRota | Pedidos e rotas offline -- buscar, remover com dependencias, detectar pedido em execucao | | IRepositorioSolicitacaoPeca | Solicitacoes de peca pendentes de sincronizacao | **Como criar um novo repositorio:** 1. Criar a interface em Data/Repositorios/IRepositorioMinhaEntidade.cs 2. Implementar em Data/Repositorios/RepositorioMinhaEntidade.cs injetando OfflineDbContext 3. Registrar no DI em Extensions/ServicosExtensions.cs ## 07 -- HttpClients & Refit Clientes HTTP sao configurados exclusivamente em Extensions/HttpClientExtensions.cs. O helper ConfigureApiDefaults aplica BaseAddress, timeout e o AuthenticationHandler automaticamente. | Cliente | Timeout | Uso | |---------|---------|-----| | IDoncMobileApiService | TimeoutPadrao | API principal -- rotas, pedidos, mensagens | | IRelatorioRecebimentosService | TimeoutPadrao | Relatorios de recebimento | | IArquivoService | TimeoutUpload | Upload de fotos e arquivos | | IEstoqueOnlineService | TimeoutEstoque | Consulta de estoque (HttpClient manual) | **AuthenticationHandler e automatico** -- Qualquer cliente criado com ConfigureApiDefaults() ja inclui o AuthenticationHandler, que injeta o token JWT em todas as requisicoes. **Timeouts disponiveis:** - TimeoutPadrao = 30 segundos - TimeoutUpload = 120 segundos - TimeoutEstoque = 45 segundos ## 08 -- Injecao de Dependencia O registro de DI e dividido em cinco arquivos de extensao: | Arquivo | O que registra | Lifetime padrao | |---------|---------------|-----------------| | BancoExtensions.cs | OfflineDbContext, migrations | Scoped | | HttpClientExtensions.cs | Clientes Refit + AuthenticationHandler | Transient | | ServicosExtensions.cs | Servicos de negocio, repositorios, navegacao, dialogos | Scoped / Singleton | | ViewModelsExtensions.cs | Todas as ViewModels | Transient (exceto MensagensHubViewModel = Singleton) | | ViewsExtensions.cs | Todas as Pages XAML | Transient | **Lifetimes:** | Lifetime | Quando usar | Exemplos | |----------|-------------|----------| | Transient | Uma instancia nova por resolucao | ViewModels, Views, AuthenticationService | | Scoped | Uma instancia por escopo | OfflineDbContext, repositorios, SyncService | | Singleton | Uma instancia para toda a vida do app | INavegacaoService, IStorageService, BackgroundSyncService | **MauiProgram.cs (estrutura esperada):** ```csharp builder.Services .AddBanco() // BancoExtensions .AddApiClients() // HttpClientExtensions .AddServicos() // ServicosExtensions .AddViewModels() // ViewModelsExtensions .AddViews(); // ViewsExtensions ``` ## 09 -- Classes Parciais ViewModels grandes sao divididas em arquivos partial class por responsabilidade. **Exemplo: ExecucaoServicoViewModel (10 arquivos)** ``` ViewModels/ExecucaoServico/ ├── ExecucaoServicoViewModel.cs -- campos, construtor, propriedades, InicializarAsync ├── ExecucaoServicoViewModel.Navegacao.cs -- comandos de navegacao ├── ExecucaoServicoViewModel.JobItem.cs -- logica de job items ├── ExecucaoServicoViewModel.Deslocamento.cs -- GPS e deslocamento ├── ExecucaoServicoViewModel.Assinatura.cs -- captura de assinatura ├── ExecucaoServicoViewModel.Fechamento.cs -- finalizacao do servico ├── ExecucaoServicoViewModel.Integracoes.cs -- chamadas de API externas ├── ExecucaoServicoViewModel.Cache.cs -- caching de dados locais ├── ExecucaoServicoViewModel.Problemas.cs -- relatorio de problemas └── ExecucaoServicoViewModel.Fotos.cs -- captura e upload de fotos ``` > **ALERTA:** Cada arquivo parcial precisa de seus proprios using statements. Nao existe "using compartilhado" entre arquivos parciais em C#. **Quando dividir em parciais:** | Situacao | Decisao | |----------|---------| | ViewModel com ate ~150 linhas | Manter em arquivo unico | | ViewModel com 150-400 linhas e responsabilidades claras | Avaliar -- dividir se houver 2+ grupos distintos | | ViewModel acima de 400 linhas | Dividir em parciais por responsabilidade | | ViewModel com comandos de diferentes dominios | Sempre dividir | ## 10 -- Checklist -- Nova Feature 1. ViewModel herda de BaseViewModel. Constructor chama base(logger, navegacao, dialog). 2. Todos os RelayCommands usam ExecutarAsync. Nenhum try/catch manual. 3. Navegacao via Navegacao.* (nunca Shell.Current). 4. Dialogos via Dialog.* (nunca DisplayAlert direto). 5. Acesso offline via repositorio (nunca OfflineDbContext direto). 6. ViewModel registrada em ViewModelsExtensions.cs. 7. View (Page) registrada em ViewsExtensions.cs. 8. Rota registrada em AppShell.xaml.cs (se page nova). 9. Novos HttpClients configurados em HttpClientExtensions.cs. 10. Build sem erros (0 erros de compilacao). Avisos MVVMTK0034 e CS0618 sao pre-existentes e podem ser ignorados. > **ALERTA sobre idioma:** Nomes em portugues. Excecao: strings de rota Shell em ingles (legado).