Coin Venture
Exchange de criptomonedas multiplataforma en Flutter, con precios en vivo de Binance y balances liquidados en Firestore
Rol · Desarrollo integral: arquitectura Clean + BLoC, sistema de diseño propio, integración con la API pública de Binance y capa de datos sobre Firebase
Los precios no son simulados: la app consume los endpoints públicos `/api/v3/ticker/24hr` y `/api/v3/klines` de Binance a través de Dio, refresca el listado cada 30 segundos y dibuja un sparkline de 168 velas horarias (7 días) por cada par en pantalla.
La liquidación de una orden se resuelve dentro de una transacción de Firestore: valida fondos, actualiza el mapa de balances del usuario y escribe el mismo movimiento en las subcolecciones `transactions` y `orders`, todo de forma atómica.
El código sigue Clean Architecture estricta: 6 módulos de features, cada uno partido en `domain`, `data` y `presentation`, con 16 casos de uso, 6 repositorios abstractos y 16 data sources conectados con get_it.
La navegación usa go_router con un `AuthGuard` que escucha el stream de sesión de Firebase y redirige automáticamente entre `/login` y `/home/markets`, más feature flags que montan o desmontan rutas completas en tiempo de arranque.
Tres frentes que rara vez conviven
El problema
Construir un exchange de criptomonedas exige tres cosas que raramente conviven en un proyecto pequeño: datos de mercado reales y frescos, un motor de saldos que no pueda descuadrarse, y una sola base de código que sirva igual en navegador y en móvil. Las alternativas obvias fallan en algún punto: mockear precios convierte la app en una maqueta sin valor, y liquidar órdenes con lecturas y escrituras sueltas contra la base de datos abre la puerta a saldos negativos o a operaciones registradas dos veces.
La solución
Coin Venture resuelve cada frente con una decisión explícita. Los datos vienen de la API pública de Binance mediante un cliente Dio con reintentos escalonados (500 ms, 1 s, 2 s) y timeouts de 10 s, filtrados a pares con cotización soportada (USDT, BUSD, USDC, FDUSD, TRY) y recortados al top 10 para no saturar la vista. Los saldos viven en el documento `users/{uid}` de Firestore y se mueven únicamente dentro de `runTransaction`, que rechaza la orden si no hay fondos y escribe balance, transacción y orden en el mismo commit. Y toda la app es un único proyecto Flutter con seis targets de plataforma, un sistema de diseño propio (colores, tipografía, espaciado y siete widgets compartidos) y una capa de almacenamiento de sesión que cambia sola entre `flutter_secure_storage` en nativo y `SharedPreferences` en web.
Seis piezas que sostienen el precio
Datos que llegan solos, saldos que no se descuadran y una única base de código para seis plataformas.
Mercados en vivo desde Binance
Un Timer periódico en el MarketsBloc dispara un refresco cada 30 segundos contra `/api/v3/ticker/24hr`, recalcula capitalización agregada, volumen 24 h, dominancia de BTC y número de monedas, y repinta la tabla sin mostrar el spinner cuando ya hay datos en pantalla.
Liquidación atómica en Firestore
`FirestoreTradeDataSource.persistOrder` corre dentro de `runTransaction`: valida fondos con un margen de 1e-9, ajusta los saldos de base y cotización, y escribe el movimiento en `users/{uid}/transactions` y `users/{uid}/orders` en el mismo commit. Si algo falla, nada se aplica.
Clean Architecture de seis módulos
auth, markets, wallet, trade, history y settings, cada uno con sus capas domain/data/presentation. 12 entidades, 16 casos de uso, 6 repositorios abstractos con implementaciones intercambiables y `Either<Failure, T>` de dartz como contrato de error en toda la app.
Sparklines de 7 días dibujadas a mano
Para cada par visible se piden 168 velas horarias en paralelo con `Future.wait` y se renderizan con un CustomPainter propio: trazo redondeado, relleno degradado del 35 % al 5 % y color verde o rojo según el signo de la variación de 24 h.
Sesión protegida por guard reactivo
`AuthGuard` extiende ChangeNotifier y se suscribe al stream de usuario de Firebase; go_router lo usa como `refreshListenable` y `redirect`, de modo que cerrar sesión expulsa al login desde cualquier ruta sin código extra en las pantallas.
Un código, seis plataformas
El repositorio incluye targets de Android, iOS, web, macOS, Windows y Linux. Las diferencias de plataforma se aíslan en fábricas: `TokenStore.create()` elige almacenamiento seguro o SharedPreferences según `kIsWeb`, y el datasource de auth ajusta persistencia e inicialización de Google Sign-In del mismo modo.
El repositorio, cotizado
Cada cifra contable del proyecto dibujada como una vela: cuerpo, mecha y una escala que sólo mide código.
115
archivos Dart en lib/
7.821
líneas de código Dart
6
módulos de features con capas domain/data/presentation
8
rutas registradas en go_router
Arrastra en horizontal para recorrer las once velas.
La sala de trading, recreada
Reconstrucción en HTML y CSS de las pantallas reales: mismos colores, misma disposición, mismos textos.
Mercados — tabla en vivo con sparkline de 168 velas por par
Coin Venture
Inicia sesión en tu cuenta
¿No tienes cuenta? Regístrate
Login
Precio actual
$67.482,10
Gráfico de precio (1h)
Estadísticas del activo
Trading
Detalle de activo y trading
Mi Portafolio
Resumen de tus activos y balance
$12.480,35
Balances
Portafolio
Historial de operaciones
Registro completo de tus transacciones
Sin transacciones aún
Comienza a operar para ver tu historial aquí
Historial de operaciones
Arrastra para ver las cuatro pantallas móviles.
Las maquetas reproducen la interfaz real; los datos que muestran son de ejemplo.
El libro de lo que ya funciona
Dieciséis funcionalidades del repositorio, listadas como el libro de órdenes de la propia app.
Tres carriles, seis módulos
Pasa el cursor por un carril o por un módulo: la intersección es exactamente el archivo que existe en el repositorio.
lib/ al completo
Monorepo Flutter con `lib/` dividido en tres zonas. `core/` concentra lo transversal: configuración (`AppEnv` con flavor por `String.fromEnvironment`, `FeatureFlags`, tema Material 3), inyección de dependencias (`configureDependencies()` registra con get_it el entorno, Dio, FirebaseAuth, FirebaseFirestore, los data sources, los repositorios, los 16 casos de uso y los blocs como factories), red (`buildDioClient` con AuthInterceptor y RetryInterceptor), errores (`Failure`/`Exception` tipados), enrutado (`AppRouter` + `AuthGuard`), seguridad (`TokenStore` con fábrica web/nativo) y utilidades (`Formatters` sobre intl, `Debouncer`). `features/` agrupa seis módulos —auth, markets, wallet, trade, history, settings— y cada uno repite el mismo triple corte: `domain` con entidades puras, repositorio abstracto y casos de uso invocables; `data` con modelos de mapeo, data sources concretos (Binance sobre Dio, Firestore, y variantes mock para auth y trade) e implementación del repositorio que devuelve `Either<Failure, T>`; y `presentation` con bloc/cubit, eventos, estados y páginas. `shared/` aporta el sistema de diseño: `AppColors`, `AppTypography` (Poppins, nueve escalas), `AppSpacing` y siete widgets reutilizables (AppPage, AppCard, AppButton con cuatro variantes, AppTextField, AppSectionHeader, AppShell y MiniSparkline). La navegación es un `GoRouter` con `/login` y `/register` sueltos y un `ShellRoute` que envuelve `/home/markets`, `/home/markets/:symbol`, `/home/portfolio`, `/home/trade` y —según feature flags— `/home/history` y `/home/settings` dentro del `AppShell` con barra superior fija. En Firestore, cada usuario es un documento `users/{uid}` con un mapa `balances`, un objeto de ajustes y las subcolecciones `transactions` y `orders`.
Firestore
users/{uid}
├─ balances { BTC, ETH, USDT… }
├─ settings { perfil, idioma… }
├─ transactions/
│ └─ {orderId}
└─ orders/
└─ {orderId}Balance, transacción y orden se escriben en el mismo commit de runTransaction. Si algo falla, nada se aplica.
Seis cosas que costaron
Toca cada fila para ver cómo se resolvió.
Solución
Toda la liquidación se movió dentro de `_firestore.runTransaction`: se lee el documento del usuario, se valida que haya fondos con una tolerancia de 1e-9 (lanzando `CacheException` con mensaje legible si no los hay), se ajustan los saldos de base y cotización, y se escriben en el mismo commit la actualización de balances, el documento en `transactions` y el documento en `orders`, ambos con el id de la orden.
Los chips de la barra de orden
Cada dependencia con la misma forma que los filtros de la tabla de mercados.
Núcleo
Estado y arquitectura
Red y datos
Backend y autenticación
Persistencia local
Navegación y UI
Herramientas de desarrollo
Plataformas objetivo
Coin Venture
¿Quieres algo así para tu producto?
Precios reales de Binance, saldos que se mueven sólo dentro de una transacción y un mismo código corriendo en seis plataformas. Si tu producto necesita datos de mercado en vivo o un motor de saldos que no se descuadre, es terreno conocido.