Tino

App de finanzas personales para Android donde los datos viven cifrados en el dispositivo, sin cuentas ni backend de usuarios — construida, publicada y operada en producción por una sola persona: código, compliance legal y monitoreo.

Rol: Desarrollador móvil (solo, de punta a punta)2025–2026Solo — código, compliance y operación
React Native 0.85 (New Architecture / Fabric + Hermes)Expo SDK 56TypeScript (strict)Drizzle ORM + op-sqlite (SQLCipher)Zustandexpo-routerCloudflare Workers (Hono)Claude API (Anthropic)Sentry + Android Vitals + UptimeRobot
100%
local-first, sin backend de usuarios
180+
tests (unit + componente)
73
commits atómicos

01 · Contexto y problema

Tino es un copiloto financiero para Android: los datos del usuario viven cifrados en el propio dispositivo, sin registro ni backend de usuarios — la privacidad es una propiedad de la arquitectura, no una promesa en la política de privacidad.

No es un repo de práctica: lo llevé de código a producción yo solo. Eso incluyó el application id, LGPD/CDC, Términos de Uso y Política de Privacidad (sitio propio), la ficha completa en Play Console (Data Safety, clasificación de contenido, declaración de funciones financieras), un pipeline de identidad visual generado por script, y monitoreo real en producción.

El problema técnico central: categorizar gastos con IA sin mandar cada transacción a un LLM (costo, latencia) ni exponer datos sensibles (CPF/CNPJ, tarjeta, PIX, email, teléfono) — en una app que no tiene backend propio donde apoyarse.

Restricciones

Local-first: sin cuentas ni backend de usuariosDatos financieros cifrados en reposo (SQLCipher)PII nunca cruda hacia el proveedor de IAGasto de IA acotado del lado del servidorUn solo desarrollador: código, legal y operaciónAndroid gama media-baja incluida

02 · Decisiones técnicas

1

Almacenamiento local cifrado

Problema

Los datos financieros debían vivir solo en el dispositivo pero seguir siendo consultables con relaciones estructuradas (categorías, presupuestos, transacciones) y cifrados en reposo, sin componente de servidor para el modelo de datos.

Opciones

AsyncStorage/MMKV plano · Realm cifrado · SQLite sin cifrar + capa propia · Drizzle ORM sobre op-sqlite (SQLCipher)

Decisión ✓

Drizzle ORM sobre op-sqlite con SQLCipher: base relacional cifrada en reposo, migraciones tipadas, sin depender de un backend para el esquema de datos.

Trade-off

El migrador que trae el ORM tenía un bug real con DDL + defaults en SQLite, encontrado validando contra una base real. Se resolvió escribiendo un migrador propio en vez de confiar en el estándar.

2

Categorización con IA sin filtrar PII ni pagar por cada transacción

Problema

Quería categorización inteligente de gastos, pero mandar cada transacción a un LLM es caro, lento y expone datos sensibles (CPF/CNPJ, tarjeta, PIX, email, teléfono) que un copiloto financiero no puede filtrar.

Opciones

IA por transacción sin filtro · reglas fijas sin IA · motor híbrido (reglas → cache → cola de IA en lote) con anonimización previa

Decisión ✓

Motor híbrido: reglas determinísticas resuelven la mayoría de las transacciones sin tocar red; lo que no matchea entra a una cola y se manda en lote — Haiku para clasificación, Opus para el chat —, siempre después de una función pura de anonimización de PII, testeada de forma aislada.

Trade-off

Más piezas que 'mandalo todo a la IA', pero la mayoría de las transacciones se resuelven gratis y en el dispositivo, y ningún dato sensible sale crudo.

3

Backend mínimo: proxy stateless en vez de backend de usuarios

Problema

Necesitaba usar la API de Claude sin exponer la key en el cliente, para una app que se declara local-first y no quiere construir un backend de usuarios completo.

Opciones

Backend completo con cuentas · SDK de IA embebido en el cliente (key expuesta) · Cloudflare Workers como proxy stateless

Decisión ✓

Cloudflare Workers (Hono) como proxy stateless: la key de la IA vive solo en el Worker, con rate limiting y techo de gasto del lado del servidor; el Worker no persiste datos del usuario, solo reenvía la llamada ya anonimizada.

Trade-off

Un salto de red extra para la IA, a cambio de cero superficie de backend de usuarios que mantener y gasto de IA acotado aunque el cliente esté comprometido.

Arquitectura

UI · expo-router (file-based)
App · Zustand + hook de reactividad local propio
Domain · motor de categorización (reglas → cache → cola IA)
Infra · Drizzle/op-sqlite (SQLCipher) on-device + Cloudflare Worker (proxy IA)

03 · Publicar en solitario, de punta a punta: cuando el trabajo después del código es la mitad del proyecto

Llevar Tino de un repo a la Play Store yo solo significó tratar todo lo que normalmente se reparte entre roles distintos (legal, diseño, DevOps, PM) con el mismo rigor que el código.

Eso fue application id, LGPD/CDC, Términos de Uso y Política de Privacidad publicados en un sitio propio, y la ficha completa en Play Console: Data Safety, clasificación de contenido y la declaración específica de funciones financieras.

También un pipeline de identidad visual (ícono, splash y feature graphic generados por script a partir de una única definición) para no mantener assets a mano, y monitoreo real en producción: Sentry configurado opt-in con un scrubber de PII —no es negociable en una app que maneja datos financieros—, Android Vitals y UptimeRobot vigilando el backend.

Resultado: una app financiera en producción en la Play Store, con prueba cerrada con testers reales en curso, operada de punta a punta por una sola persona.

04 · De un repo a la Play Store, solo

Application id, LGPD/CDC, Términos de Uso y Política de Privacidad en un sitio propio — no plantillas genéricas, adaptados a lo que la app realmente hace con datos financieros.

Ficha completa en Play Console: formulario de Data Safety, clasificación de contenido y la declaración de funciones financieras que Google exige para este tipo de apps.

Pipeline de identidad visual: ícono, splash y feature graphic generados por script desde una única definición, para no depender de retocar assets a mano en cada release.

Monitoreo de producción real: Sentry con scrubber de PII (opt-in), Android Vitals para estabilidad y rendimiento, y UptimeRobot vigilando el backend — más una prueba cerrada con testers reales en curso.

Demo interactiva

Próximamente: ejecuta el código y míralo correr en vivo, sin instalar nada.

main.dart
Run

Editor en vivo (DartPad) — próximamente

Próximamente

04 · Resultados · antes / después

tests (unit + componente)
0
180+
commits atómicos
73
PII expuesta a la IA
sin filtro
0 (anonimizada antes de cualquier llamada)
cobertura en el núcleo (CI)
sin gate
gate obligatorio

05 · Retrospectiva

Escribiría el migrador propio desde el día 1 en vez de confiar en el del ORM: el bug con DDL + defaults en SQLite solo apareció validando contra una base real, y costó tiempo debuggear algo que no era mi lógica.

Trataría el scrubber de PII como función pura y testeada aislada desde el primer commit de IA, no después: es la pieza que menos margen de error tolera, y por eso mereció ese cuidado extra desde el principio.

Volvería a elegir un proxy stateless en un Worker antes que un backend completo: en una app que se vende como local-first, cualquier pieza de servidor que no sea estrictamente necesaria es una promesa rota.

Caso anterior
Siguiente caso