← Voltar para publicações InkAgenda: Flutter com disciplina de servidor enterprise

InkAgenda: A Agenda que Herdou a Disciplina do Servidor

Logo Cara Core Cara Core Informática 25 de janeiro de 2026
Tempo estimado de leitura: ~10 minutos

Introdução: Quando um Tatuador Merecia o Mesmo Rigor que um Banco

No artigo anterior, falamos sobre como a Cara Core aplica 20 anos de disciplina empresarial no Flutter. Mas isso era teoria. Agora é hora de contar a história real.

Existe um tatuador em São Paulo. Profissional sério, respeitado em seu estúdio. Ele agendava seus clientes em um caderno de papel. Sim, papel. Porque cada app de agenda que testou era cheio de bugs, travava sem razão, e os dados desapareciam quando mudava de telefone.

Esse tatuador mereceria algo melhor. Mereceria o que um banqueiro tem. Um sistema que não falha, que não perde dados, que funciona offline quando a rede cai, e que o acompanha de qualquer lugar — do estúdio, de casa, do celular.

Então, a Cara Core criou o InkAgenda.

A Premissa: Um estúdio de tatuagem não é menos importante que um banco. Merecia a mesma engenharia, rigor e confiabilidade.

O que você vai ler aqui é como aplicamos, na prática, cada princípio que falamos no artigo anterior. Não é teoria. É código rodando em Windows, Android, Web e iOS. É dados sincronizados sem perder um byte. É um app que funciona offline e recupera-se quando a rede volta. É um projeto que pode viver 10 anos sem precisar ser reescrito.

1. O Problema: Por Que Agendas Comuns Falham

Antes de falarmos sobre a solução, precisamos entender o problema. Se você desenvolveu apps de agenda antes, conhece os pontos de dor:

Sincronização: O Pesadelo Silencioso

Um cliente marca uma tatuagem. O app registra. A rede cai — porque em São Paulo, a rede cai frequentemente. O app trava? Ele perde o agendamento? Ele não sincroniza quando a rede volta?

A maioria dos apps falha aqui. Porque sincronização é fácil quando a rede está estável. É um pesadelo quando não está.

Para o tatuador, isso é desastre. Se ele perder um agendamento, perde um cliente. Perde renda.

Múltiplos Dispositivos: O Caos Real

O tatuador inicia o dia no Windows (está no estúdio). Sai para almoço, leva o celular Android. À noite, acessa pela web. Cada um desses "mundos" precisa estar sincronizado. Se ele marca um cliente no Windows, o Android precisa saber instantaneamente. Se alguém marca pelo website, tanto Windows quanto Android precisam estar atualizados.

Fazer isso sem perder dados, sem conflitos, sem confusão — é arquitetura. Puro, simples, e absolutamente crítico.

Confiabilidade: A Confiança que o Caderno Tinha

Um caderno de papel não trava. Não perde dados. Ele é lento (você tem que virar páginas), mas é confiável. O app do tatuador merecia ter essa mesma confiança, mas rápido. Muito rápido.

A Descoberta: A maioria dos apps falha não porque a tecnologia é ruim. Falha porque a arquitetura foi construída assumindo que a rede sempre funciona, que o dispositivo nunca muda, que os dados nunca conflitam.

2. A Solução: Clean Architecture em Flutter

A Cara Core construiu o InkAgenda em camadas. Três, para ser exato. E cada camada tem um trabalho:

Domain Layer: O Coração Imune a Modismos

No topo da pirâmide (ou melhor, no coração do sistema) vive a Domain Layer. Aqui vivem as regras do negócio de um estúdio de tatuagem. Puro, independente, sem frameworks, sem dependências externas.

// Domain Layer: puro negócio
abstract interface class AgendamentoRepository {
  Future<void> criarAgendamento(Agendamento agendamento);
  Future<List<Agendamento>> obterAgendamentosDoMes(DateTime mes);
  Future<Agendamento?> obterAgendamentoPorId(String id);
  Future<void> atualizarAgendamento(Agendamento agendamento);
}

// A lógica de negócio não conhece Firebase, SQLite, ou nada do mundo externo
class CriarAgendamentoUseCase {
  final AgendamentoRepository repository;
  
  CriarAgendamentoUseCase(this.repository);
  
  Future<void> executar(Agendamento agendamento) async {
    // Validações de negócio aqui
    if (agendamento.data.isBefore(DateTime.now())) {
      throw Exception('Não pode agendar no passado');
    }
    
    // A lógica de negócio não sabe como persiste
    await repository.criarAgendamento(agendamento);
  }
}

Por que isso importa? Porque quando você quer testar essa lógica, não precisa de Firebase. Não precisa de rede. Você cria um mock repository em memória e testa em milissegundos:

// Teste simples: sem rede, sem dependências
void main() {
  test('Não permite agendamento no passado', () async {
    final mockRepo = MockAgendamentoRepository();
    final useCase = CriarAgendamentoUseCase(mockRepo);
    
    expect(
      () => useCase.executar(Agendamento(data: DateTime(2020, 1, 1))),
      throwsException,
    );
  });
}

Isso é rapidez e confiança em uma só coisa.

Data Layer: A Flexibilidade Que Dura

Na camada de dados, vive a implementação concreta. Hoje é SQLite + Firebase. Amanhã pode ser apenas SQLite, ou PostgreSQL, ou qualquer coisa. A camada de negócio não liga.

// Data Layer: implementação concreta
class FirebaseSQLiteAgendamentoRepository implements AgendamentoRepository {
  final FirebaseDatabase _firebase;
  final Database _sqlite;
  final ConnectionManager _connectionManager;
  
  @override
  Future<void> criarAgendamento(Agendamento agendamento) async {
    // 1. Tenta salvar no Firebase
    try {
      await _firebase.ref('agendamentos/${agendamento.id}').set(agendamento.toJson());
    } on FirebaseException {
      // 2. Se falhar, guarda localmente no SQLite
      await _sqlite.insert('agendamentos', agendamento.toMap());
      
      // 3. Marca para sincronizar depois
      await _connectionManager.registrarPendente(agendamento.id, 'CREATE');
    }
  }
}

Veja o que aconteceu: quando a rede cai, o app não falha. Salva localmente e marca para sincronizar depois. Transparente para o usuário.

Presentation Layer: A Interface Que Responde

A camada de apresentação — a UI — não conhece Firebase nem SQLite. Conhece apenas a Domain Layer. E reage a mudanças de estado de forma previsível, usando Riverpod (sim, aquele que mencionamos no artigo anterior).

// Presentation: reativa, limpa, testável
final agendamentosProvider = FutureProvider.autoDispose((ref) async {
  final useCase = ref.watch(criarAgendamentoUseCaseProvider);
  return await useCase.obterAgendamentosDaSemana();
});

class AgendaScreen extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final agendamentos = ref.watch(agendamentosProvider);
    
    return agendamentos.when(
      data: (list) => ListView(children: [
        for (final agendamento in list)
          AgendamentoCard(agendamento: agendamento)
      ]),
      loading: () => const CircularProgressIndicator(),
      error: (err, stack) => Text('Erro: $err'),
    );
  }
}

A UI não toma decisões de negócio. Apenas apresenta dados e dispara ações. O resto, a Domain Layer cuida.

3. Sincronização: O Problema Resolvido

Agora chegamos no prato principal: sincronização em tempo real, offline-first.

O Algoritmo: Timestamp + Conflict Resolution

Cada documento que vive no Firebase tem um timestamp. Quando você edita algo localmente enquanto offline, o app também registra um timestamp local. Quando a rede volta:

  1. Push local: Qualquer mudança feita offline é enviada.
  2. Pull remoto: Qualquer mudança feita por outro dispositivo é baixada.
  3. Resolve conflitos: Se a mesma coisa foi editada em dois lugares, last-write-wins — o timestamp mais recente vence. Simples e previsível.
  4. Marca como sincronizado: Uma vez que tudo está em ordem, marca como "seguro".

Aqui está o código real que implementa isso:

// Sincronização com resolução de conflitos
class SyncService {
  Future<void> sincronizar() async {
    final pendentes = await _sqlite.getPendentes();
    
    for (final item in pendentes) {
      try {
        // Obtém versão remota
        final remota = await _firebase.get(item.id);
        
        if (remota == null) {
          // Não existe remota, cria
          await _firebase.set(item.toJson());
        } else if (item.timestamp.isAfter(remota['timestamp'])) {
          // Local é mais novo, sobrescreve remota
          await _firebase.update(item.toJson());
        }
        // Se remota é mais nova, ignora local (pode ser feito merge depois)
        
        // Marca como sincronizado
        await _sqlite.markSync(item.id);
      } on Exception {
        // Se falhar, tenta de novo depois
        continue;
      }
    }
  }
}

Resultado: se o tatuador marca um agendamento offline, é salvo instantaneamente no dispositivo. Quando a rede volta, sincroniza em segundos. Se outro tatuador do estúdio marcou algo ao mesmo tempo, o sistema resolve sem perder dados.

4. Múltiplas Plataformas, Uma Lógica

O InkAgenda roda em:

A mesma Domain Layer, a mesma lógica de negócio, roda em todas.

Por quê? Porque a lógica de validação, de sincronização, de resolução de conflitos — é código compartilhado. A Cara Core escreveu uma vez, testa uma vez, mantém uma vez. A Data Layer muda conforme a plataforma (Android usa um driver SQLite diferente de Windows), mas a regra de negócio é a mesma.

O Benefício: Menos bugs. Menos tempo de desenvolvimento. Menos confusão de "funciona no Android mas não no Windows".

5. Inspirações do Java EE Que Ainda Vivem

Se você ler o código do InkAgenda, vai reconhecer coisas do mundo Java:

Não é coincidência. É herança. A Cara Core trouxe os 20 anos de aprendizado do Java EE para o Flutter. O resultado é um app que os Javas devlopers entendem, que os testes cobrem, que não apodrece.

6. Resultados Reais

Números falam mais que palavras:

Antes (Caderno): Agendamentos manuais, sem histórico de finanças, sem visibilidade de demanda, impossível expandir sem contratar mais gente só para cuidar da agenda.

Depois (InkAgenda): Agendamentos digitais, sincronizados em 3 dispositivos, histórico completo, relatório de faturamento em tempo real, alertas de horários vazios.

O tatuador agora sabe exatamente quanto faturou no mês. Consegue ver os horários que não estão preenchidos. Pode deixar a agenda aberta para clientes marcarem online. E tudo sincroniza sem perder um byte.

7. O Que Falta (E Por Quê)

O InkAgenda ainda está em desenvolvimento. Não é "pronto" porque "pronto" é um mito em software. Mas o que existe é sólido. Funciona. Confiável.

Planejamos:

Mas a base está feita. E quando adicionar essas features, a arquitetura vai absorver sem tremendo.

Transparência: O código-fonte do InkAgenda é privado neste momento. Mas a experiência, os desafios e as soluções descritas aqui refletem o projeto real em desenvolvimento.

Conclusão: Quando a Disciplina Encontra a Criatividade

Se você chegou até aqui, talvez esteja pensando: "tudo bem, legal, mas por que me importa? Eu não desenvolvo agenda para tatuador."

Talvez não. Mas você provavelmente desenvolve algo que precisa sincronizar dados. Algo que precisa funcionar offline. Algo que precisa viver mais de três anos sem ser reescrito.

E a lição do InkAgenda é simples: isso não é mágico, é engenharia. É aplicar padrões consagrados. É recusar atalhos tentadores. É dizer "não" ao código que parece rápido mas é frágil.

O InkAgenda não é o primeiro app Flutter com Clean Architecture. Mas é um que realmente funciona, porque foi construído por gente que já tinha feito isso antes — em Java, em grandes sistemas, em ambientes onde falhar custa dinheiro de verdade.

Essa experiência, esse rigor, esse entendimento profundo de por quê as coisas funcionam — é o que diferencia um projeto que dura de um que morre em um ano. É o que diferencia um tatuador confiante em seus números de um anotando em caderno.

A disciplina do servidor chegou ao celular. E o resultado fala por si.


Hashtags

#InkAgenda #Flutter #CleanArchitecture #SOLID #OfflineFirst #Sincronização #CaraCore #SoftwareEngineering #MobileArchitecture #DesignPatterns

Contato

Gostou do conteúdo?
Conecte-se conosco no LinkedIn para mais conteúdos sobre arquitetura de software, engenharia e inovação em tecnologia!
Seguir no LinkedIn

Artigo publicado em 25 de janeiro de 2026
© 2026 Cara Core Informática. Todos os direitos reservados.