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.
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.
Antes de falarmos sobre a solução, precisamos entender o problema. Se você desenvolveu apps de agenda antes, conhece os pontos de dor:
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.
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.
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 Cara Core construiu o InkAgenda em camadas. Três, para ser exato. E cada camada tem um trabalho:
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.
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.
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.
Agora chegamos no prato principal: sincronização em tempo real, offline-first.
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:
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.
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.
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.
Números falam mais que palavras:
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.
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.
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.
Artigo publicado em 25 de janeiro de 2026
© 2026 Cara Core Informática. Todos os direitos reservados.