Oracle3
Oracle3: 예측시장 거래 엔진·MCP 서버
개인 재정 관리 안드로이드 앱, 수입·지출 기록·자산·소비 구분, 엑셀 내보내기 지원, 계정·서버·광고 없음
App Android de finanzas personales. Registra lo que entra y lo que sale, separa el patrimonio del consumo, y exporta todo a Excel. Sin cuentas, sin servidor, sin publicidad.
금융Kotlin · HTMLGitHub @carlosalbertoxw온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
App Android de finanzas personales: registra lo que entra y lo que sale, lo clasifica, y te dice en qué se te está yendo el dinero. Ollin es "movimiento" en náhuatl, y es el glifo del calendario mexica que representa el cambio — que es exactamente lo que registra un libro de finanzas.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
El libro de tus movimientos. App Android de finanzas personales: registra lo que entra y lo que sale, lo clasifica, y te dice en qué se te está yendo el dinero. Ollin es "movimiento" en náhuatl, y es el glifo del calendario mexica que representa el cambio — que es exactamente lo que registra un libro de finanzas. No está en Google Play: el APK se instala a mano, y el sitio explica cómo. La app comprueba una vez al día si salió una versión nueva y lo dice en Acerca de; se puede apagar en Ajustes. Comprobar que el APK es el buenoREADME 전문 보기 ↗
El libro de tus movimientos.
App Android de finanzas personales: registra lo que entra y lo que sale, lo clasifica, y te dice en qué se te está yendo el dinero. Ollin es "movimiento" en náhuatl, y es el glifo del calendario mexica que representa el cambio — que es exactamente lo que registra un libro de finanzas.
No está en Google Play: el APK se instala a mano, y el sitio explica cómo. La app comprueba una vez al día si salió una versión nueva y lo dice en Acerca de; se puede apagar en Ajustes.
Todas las versiones van firmadas con la misma llave. Esta es la huella de su certificado, y está aquí —en el repositorio, no solo en el sitio— para que se pueda comparar por un camino distinto del que trajo el archivo:
SHA-256 del certificado de firma: 047eb6bdc9de0724e50e9b10bf86bd7bd5d31d9c9cb6f48c1d49ac46ceea638a
apksigner verify --print-certs ollin-finanzas-x.y.z.apk
La línea certificate SHA-256 digest tiene que decir exactamente eso. Si no coincide, no
lo instales. En el teléfono, sin computadora, lo comprueba también
AppVerifier. El flujo de publicación se niega
a publicar un APK firmado con otra llave.
Y para comprobar además que el APK salió del flujo de publicación de este repositorio —de qué commit y con qué workflow—, cada release lleva una atestación de procedencia:
gh attestation verify ollin-finanzas-x.y.z.apk --repo carlosalbertoxw/ollin-finanzas
| Movimientos | Entradas, salidas, transferencias y saldos de apertura. Captura con listas cerradas: sin categorías escritas a mano que después no coinciden entre sí. La lista se filtra por texto y por cuenta, y la suma del filtro se calcula en SQL sobre todo lo que cumple, no sobre lo que alcanzó a cargarse. Tocar una cuenta en el tablero entra aquí con ese filtro ya puesto. |
| Transferencias en una sola captura | Mueves dinero entre dos cuentas tuyas una vez y la app genera los dos renglones ligados por un mismo grupo. Borrar uno borra el otro, así que nunca queda media transferencia. |
| Categorías con padre e hija | La categoría clasifica y la descripción detalla. Son dos cosas distintas y viven en columnas distintas, para que un rubro repartido en cinco descripciones se vea como el rubro grande que es. |
| Patrimonio aparte del gasto | Categorías de tipo Patrimonio y cuentas de tipo Activo. Comprar terreno, cripto o un celular es trasladar patrimonio, no gastarlo, y los tableros lo separan del consumo real. |
| Contraparte derivada | No se captura si el movimiento es entre tus cuentas o con un tercero: se deduce del tipo. Traspaso o saldo inicial → propia; entrada o salida → tercero. Un campo que se captura a mano es un campo que se deja de mantener. |
| Salud de los datos | Una pantalla que revisa en continuo: tipos que contradicen al signo del importe, transferencias sin su pareja, movimientos sin categoría, medios incoherentes con la cuenta, y descripciones casi idénticas (Levenshtein ≤ 2). Varios hallazgos se reparan con un botón. |
| Presupuesto y analítica | Meta contra realidad por categoría y tendencia mensual. |
| Compromisos | Lo que ya está comprometido y aún no se paga: mensualidades, suscripciones, la renta, gastos anuales. Cada uno lleva su cuenta, su categoría, su periodicidad —de semanal a anual, o único para lo que se paga una sola vez— y la fecha del siguiente pago, y avisa antes de vencer a la hora que elijas en Ajustes. Registrar abre la captura ya llena para que corrijas el monto si cambió. El plan nunca avanza solo: se desliza la tarjeta a la derecha y se elige Cumplir o Descartar, y las dos se deshacen devolviendo el plan justo a donde estaba, incluso uno que cae en día 31. Mientras nadie decida, el pago sigue pendiente aunque se pase de fecha. Lo que ya no pide nada —un pago único resuelto, un plan que llegó a su última mensualidad— se va al : el botón de la caja, en la barra superior, cambia la pantalla a la lista de lo cerrado —solo eso, sin mezclarlo con lo pendiente— y la guarda como historia sin que siga avisando. Todo esto —registrar, cumplir, descartar— se hace igual desde el tablero, sin entrar a la lista. |
Todos los importes viven como centavos en un Long. Nunca como decimal flotante: así
un saldo cero es cero exacto y las conciliaciones cuadran.
.xlsx y reconoce los encabezados sin importar acentos ni
mayúsculas, en cualquiera de los dos esquemas que la app exporta. Al entrar corrige lo
que encuentre: alinea el tipo con el signo, empareja transferencias, crea las cuentas
que falten y clasifica por catálogo.Extendido (agrega Categoría, Año, Nota, Compromiso) o Compacto
(las ocho columnas esenciales).Registros siempre va, porque es
la fuente de las fórmulas de todas las demás.Pestañas disponibles: Balance, Ingresos - Egresos, Presupuesto, Transferencias,
Compromisos, Registros, Diccionarios.
Las hojas de análisis salen con SUMIFS vivos en vez de dinámicas. Una dinámica exige
refresco manual, se comporta distinto según la suite, y deja cachés huérfanos si exportas
solo algunas pestañas. Las fórmulas se recalculan solas y funcionan igual en Excel, WPS
Office, LibreOffice y Google Sheets. Cada celda lleva además el valor ya calculado, así
que la hoja se ve bien incluso en visores que no recalculan.
Los criterios de fecha usan DATE(año,mes,día) en vez de TEXT(...,"yyyy-mm"), que
depende del idioma de la suite.
Un solo módulo, Kotlin + Jetpack Compose (Material 3).
app/src/main/java/com/carlosalbertoxw/ollin/finanzas/
├── data/
│ ├── db/ Room: entidades, DAOs, proyecciones, catálogo semilla
│ ├── excel/ Lector y escritor de .xlsx propios, exportador e importador
│ ├── actualizaciones/ Si hay una versión nueva publicada
│ ├── notify/ Recordatorios de compromisos por vencer
│ ├── prefs/ Preferencias en DataStore
│ ├── repo/ FinanzasRepositorio: toda la escritura pasa por aquí
│ └── seguridad/ Llave de la base, PIN, control de bloqueo
├── di/ Contenedor de dependencias, a mano
├── domain/
│ ├── model/ Enums, Dinero (centavos)
│ └── usecase/ RevisaCalidad, ReparaDatos
└── ui/ Compose: pantallas, navegación, tema, componentes
Y fuera de la app:
.github/workflows/ Pruebas, release firmado y publicación del sitio
web/ El sitio de descarga (Vite), publicado en GitHub Pages
CHANGELOG.md La versión y las notas, en un solo lugar
Cada pantalla recibe sus dependencias concretas —el repositorio, los ajustes, el caso de
uso que usa—, no el contenedor entero. El único que lo conoce es OllinRaiz. Así un
ViewModel declara de qué depende y se puede construir en una prueba sin levantar la base
cifrada ni DataStore.
Decisiones que no son las de default, y por qué:
java.util.zip + SAX. Apache POI en Android pesa ~15 MB,
mete decenas de miles de métodos y obliga a desugaring; aquí el formato producido está
bajo control y el escritor completo cabe en ~400 líneas.di/Contenedor.kt) se lee de arriba a abajo y no cuesta tiempo de compilación.Long). Ver arriba.fallbackToDestructiveMigration, nunca. La base va cifrada con una llave del
Keystore que no se respalda: borrarla no es un inconveniente, es perder el libro entero.
Si falta un paso, Room se niega a abrir, y eso se arregla con una actualización.CHANGELOG.md. Un número escrito a mano en el build se olvida, y
quien instala el APK acaba viendo una versión que no corresponde a las notas que leyó.androidx.fragment declarado a mano. biometric:1.1.0 arrastra fragment:1.2.5,
anterior a la API de ActivityResult: su FragmentActivity rechaza los request codes de
más de 16 bits que genera activity:1.10.1, y cualquier selector de archivos revienta
al abrirse. Quitar esa línea de libs.versions.toml vuelve a romper importar y exportar.version.json.Nada de lo que capturas sale del teléfono. No hay cuentas, ni servidor, ni analítica. El
respaldo automático del sistema está desactivado para la base de datos a propósito:
una llave del Keystore no se puede restaurar, así que la copia llegaría ilegible. Tu
respaldo es la exportación a .xlsx, que tú decides dónde guardar.
La única llamada a internet es preguntarle al sitio del proyecto, una vez al día, si hay
una versión más nueva: un GET a un archivo estático que no manda ningún dato tuyo, ni
siquiera qué versión traes ni qué teléfono tienes — la comparación ocurre en el teléfono. El
sitio lo sirve GitHub Pages, que ve la IP de quien pregunta como con cualquier página web.
Se apaga en Ajustes, y
apagada la app no toca la red en ningún momento. Nunca descarga ni instala nada por su
cuenta: cuando hay versión nueva, Acerca de enseña un botón que abre el sitio.
Requiere JDK 17 o superior (probado con 21 y 26) y el Android SDK 37 instalado. Ver el JDK de Gradle.
./gradlew :app:assembleDebug
./gradlew :app:testDebugUnitTest
Las pruebas de ExcelRoundTripTest generan libros reales en app/build/pruebas/, útiles
para abrirlos a mano y comprobar el resultado.
La versión sale de CHANGELOG.md, no del build.gradle.kts: se lee el primer encabezado
## [x.y.z] y de ahí salen el versionName y el versionCode. Publicar una versión es
renombrar la sección [Sin publicar] y poner el tag vX.Y.Z; de ahí en adelante lo hacen
los flujos de GitHub Actions. Ver Publicación.
El sitio es un proyecto aparte y no pasa por Gradle. Pide Node 22.12 o superior
(web/.nvmrc):
cd web && npm ci && npm run dev
minSdk 26 · targetSdk 36 · compileSdk 37 · Kotlin 2.4.20 · AGP 9.4.1 · Gradle 9.8.0Entorno, comandos y convenciones con más detalle en Desarrollo.
MIT © 2026 Carlos Alberto.
Puedes usar, copiar, modificar y redistribuir el código, incluso con fines comerciales, mientras conserves el aviso de copyright. La app se entrega sin garantía de ningún tipo.
| Importar y exportar .xlsx | Tu respaldo es un libro de Excel que tú decides dónde guardar, en Ajustes → Importar y exportar. La app te lo recuerda cada semana si no lo has hecho —con una notificación y con un aviso arriba del tablero cada vez que la abres, hasta que exportes—, y otra vez cuando sale una versión nueva, que es justo cuando conviene tener una copia; el aviso abre la pantalla directo. |