尚未收集儲存庫資訊。
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
App para Android que permite gestionar las suscripciones
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Gestor de suscripciones personal para Android. Sin servidores, sin telemetría, sin red — 100% local.
🌐 Web · mis-dineros.cuzo.dev 🔒 Privacidad · mis-dineros.cuzo.dev/privacy.html
#0077B6 sobre fondo cálido tipo papel, tipografías Bricolage Grotesque y Figtree con cifras tabulares, y colores dinámicos opcionales (Material You, Android 12+)# Tests unitarios (JVM, sin dispositivo)
./gradlew test
# Lint
./gradlew lint
# APK de debug
./gradlew assembleDebug
# Tests instrumentados (requiere dispositivo o emulador API 26+)
./gradlew connectedAndroidTest
El APK de debug se genera en app/build/outputs/apk/debug/.
Single-module Clean Architecture por paquetes. Flujo de dependencias: presentation → domain ← data.
app/src/main/java/com/parra/misdineros/
├── core/ utilidades sin dependencias Android (MoneyFormatter, DateUtils, AppError)
├── domain/ modelos, interfaces de repositorio, use cases (Kotlin puro)
├── data/ Room, DataStore, mappers, backup, seeding
├── backup/ MisDinerosBackupAgent (Auto Backup)
├── notifications/ WorkManager workers y scheduler
├── presentation/ Screens + ViewModels por pantalla
├── designsystem/ MisDinerosTheme, paleta, componentes reutilizables
└── di/ módulos Hilt
| Capa | Tecnología |
|---|---|
| UI | Jetpack Compose + Material 3 |
| Navegación | Navigation Compose (type-safe routes) |
| DI | Hilt |
| Persistencia | Room + DataStore Preferences |
| Notificaciones | WorkManager |
| Gráficos | Compose Canvas (donut y barras propios) |
| Serialización | kotlinx.serialization |
El fichero exportado empieza por el magic MDB1 (4 bytes) y un byte de flags que identifica el formato:
| Formato | Flags | Cabecera | Payload |
|---|---|---|---|
| Plano | 0x00 | 5B (magic + flags) | JSON UTF-8 |
| Cifrado sin AAD | 0x01 | 33B (magic + flags + salt 16B + IV 12B) | AES-256-GCM ciphertext |
| Cifrado con AAD | 0x02 | 33B (magic + flags + salt 16B + IV 12B) | AES-256-GCM ciphertext |
Todas las exportaciones cifradas nuevas usan 0x02, que mete los 33 bytes de cabecera en GCM como AAD: así un byte de flags degradado a 0x01 o unos parámetros de KDF alterados hacen fallar el tag en vez de descifrar algo distinto. Los ficheros 0x01 anteriores se siguen descifrando sin AAD y deben poder leerse siempre; BackupCryptoTest guarda un fixture generado por una implementación independiente para garantizarlo.
El cifrado es opcional: el usuario lo activa al exportar e introduce una contraseña de al menos 8 caracteres (BackupCrypto.MIN_PASSWORD_LENGTH). La clave se deriva con PBKDF2WithHmacSHA256 (200 000 iteraciones, salt de 16 bytes e IV de 12 bytes aleatorios por fichero). Los ficheros exportados antes de que existiera la cabecera (JSON pelado) se importan sin cambios — compatibilidad total hacia atrás.
El JSON embebido tiene esta estructura (version: 1):
{
"version": 1,
"exportedAt": "2026-05-08T10:00:00Z",
"subscriptions": [...],
"categories": [...],
"fxRates": [...],
"settings": {...},
"assets": { "icon.png": "<base64>" }
}
Los iconos de usuario se embeben en assets como base64 y se restauran en filesDir/icons/ al importar.
El share sheet nativo permite enviar el fichero directamente a Telegram, LocalSend, Drive, etc. La opción "Guardar en archivos" usa SAF (CreateDocument) para elegir ubicación en el dispositivo.
Con allowBackup="true" y MisDinerosBackupAgent, el sistema sube automáticamente la BD Room, DataStore, iconos de usuario y preferencias a la cuenta Google del dispositivo (máx. 25 MB, cifrado por Google). Por eso los iconos pasan siempre por IconStorage, que los reescala a 512 px y los reencoda como JPEG: unas pocas fotos de cámara a tamaño completo agotaban la cuota y dejaban de hacerse copias en silencio. Los ficheros que dejan de estar referenciados se borran al guardar, al eliminar y en el barrido de huérfanos del arranque. El usuario puede desactivarlo desde Ajustes → Datos. La flag se persiste en SharedPreferences("auto_backup_prefs") además de DataStore, para que el agente pueda leerla de forma síncrona.
Hay dos workflows de GitHub Actions:
android.yml — corre en cada push a master/develop y en cada PR a master: tests JVM, lint y assembleDebug. Sube APK de debug, resultados de tests e informe de lint como artifacts (7 días).release.yml — se dispara con tags v*. Construye el APK y AAB firmados, verifica la firma con apksigner y publica una GitHub Release con ambos artefactos. El cuerpo de la release es el mensaje del tag anotado (primera línea como título) seguido de las notas automáticas de GitHub con los PRs desde el tag anterior.Dependabot (.github/dependabot.yml) mantiene actualizadas las dependencias de Gradle (agrupando minor/patch en un solo PR) y las acciones de los workflows, con revisión semanal.
Buenas prácticas aplicadas:
contents: read, salvo release.yml que necesita contents: write para crear releases).Tanto versionName como versionCode se derivan automáticamente del repositorio:
versionName — git describe --tags --always, sin la v inicial. Ej.: tag v1.0.0 → 1.0.0.versionCode — derivado de git describe --tags --long: major*1000000 + minor*10000 + patch*100 + commits desde la etiqueta (tope 99). Ej.: tag v1.5.1 → 1050100. Crece con cada etiqueta, que es lo que exige Play Console, y a diferencia de git rev-list --count no baja con un rebase o un force-push.Por eso el workflow de release hace fetch-depth: 0 y fetch-tags: true: sin el historial completo, ambos comandos darían valores erróneos en el runner.
Los APKs publicados en Releases están firmados con la misma clave que la build de Play Store.
Para verificar la firma de un APK descargado:
$ANDROID_HOME/build-tools/<version>/apksigner verify --print-certs mis-dineros-v1.0.0.apk
El workflow de release necesita estos secrets configurados en Settings → Secrets and variables → Actions:
| Secret | Descripción |
|---|---|
KEYSTORE_BASE64 | Keystore JKS codificado en base64: base64 -w0 release.jks |
KEYSTORE_PASSWORD | Contraseña del keystore |
KEY_PASSWORD | Contraseña del alias (igual a la del keystore si nunca se separaron) |
KEY_ALIAS | Alias de la clave dentro del keystore |