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.
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
02 · Decisiones técnicas
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.
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.
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
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.
Editor en vivo (DartPad) — próximamente
04 · Resultados · antes / después
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.