- Full Clean Architecture + MVVM with Hilt DI throughout all layers - Room v6 with SQLCipher encryption and 5 migrations (no destructive) - Items can be placed directly in a room or location (not just in a box) - Reactive detail screens: name changes update instantly via ObserveById flows - Camera permission flow: always-clickable button with proper rationale handling - Soft keyboard: imePadding on AddEditItemScreen so Notes field stays visible - Clickable items in BoxDetailScreen navigating to ItemDetailScreen - FTS4 full-text search, QR code scanning, CameraX photos with UCrop - Google Drive encrypted backup via WorkManager, Excel/PDF export - Biometric + PIN app lock, Google Play Billing freemium model - Home screen widgets: 4x1 search widget and 2x2 recent items widget - Updated docs/PROJECT_OVERVIEW.md to reflect current codebase state Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
43 KiB
📦 Box Organizer Inventory — Super Prompt para Claude Code (Español)
INSTRUCCIONES GENERALES PARA CLAUDE CODE
Vas a construir la app Android Box Organizer Inventory paso a paso, en fases numeradas.
REGLAS QUE DEBES SEGUIR SIN EXCEPCIÓN:
- Completa UNA fase entera antes de pasar a la siguiente.
- Al final de cada fase, PARA y pregunta al programador que compile y pruebe.
- No existe "botón sin acción". Cada elemento de UI que se vea debe tener código real detrás. Si aún no está implementado, no lo pongas visible. Usa
// TODOsolo en lógica interna, nunca en UI. - Si algo no compila, arréglalo antes de continuar. No des por bueno nada que dé error.
- Máximo 700 líneas por archivo .kt. Si un archivo crece más, divídelo en clases más pequeñas con responsabilidades claras.
- Sobre-explica todo en comentarios del código. No asumas que el programador sabe por qué haces algo.
- Nunca generes código parcial — cada archivo que crees debe estar completo y compilable.
CÓMO TERMINAR CADA FASE:
Al final de cada fase escribe exactamente esto (sustituyendo los puntos con las pruebas reales):
─────────────────────────────────────────
✅ FIN DE FASE [N] — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
Antes de continuar necesito que hagas esto:
COMPILACIÓN:
□ Haz Build > Make Project en Android Studio
□ Confirma que no hay errores en rojo en el panel Build
PRUEBAS MANUALES:
□ [Prueba concreta 1]
□ [Prueba concreta 2]
□ [Prueba concreta 3]
Cuando hayas hecho TODO lo anterior, escríbeme:
"Fase [N] OK" → y continúo con la fase [N+1]
"Fase [N] ERROR: [descripción]" → y lo arreglo antes de continuar
─────────────────────────────────────────
CONTEXTO DEL PROYECTO
App: Box Organizer Inventory
Propósito: Organizar objetos físicos en cajas, habitaciones y casas. El usuario hace fotos, genera QR por caja, busca objetos y exporta inventarios.
Plataforma: Android nativo (Kotlin)
Min SDK: 27 (Android 8.1) — cubre el 95%+ del mercado
Target SDK: 36
Stack técnico completo:
| Librería | Versión | Para qué |
|---|---|---|
| Kotlin | 2.0+ | Lenguaje principal |
| Room | 2.6+ | ORM base de datos |
| SQLCipher | 4.5+ | Cifrado de la BD en disco |
| Hilt | 2.51+ | Inyección de dependencias |
| Jetpack Compose BOM | 2024.12+ | UI declarativa (sin XML) |
| Material3 Compose | - | Componentes Material You |
| Compose Navigation | 2.7+ | Navegación entre pantallas |
| CameraX | 1.3+ | Cámara + captura fotos |
| ML Kit Image Labeling | 18+ | Auto-tags desde foto |
| ML Kit Barcode Scanning | 17+ | Escanear QR y barcodes |
| WorkManager | 2.9+ | Backup automático en background |
| DataStore Preferences | 1.1+ | Guardar preferencias usuario |
| Coil | 2.7+ | Cargar imágenes eficientemente |
| Apache POI Android | 5.2+ | Generar archivos Excel |
| iTextPDF / PdfDocument | - | Generar PDFs |
| Google Play Billing | 6.2+ | Compras in-app (PRO) |
| Google Drive API | 3.0+ | Backup en la nube (PRO) |
| ViewModel + StateFlow | - | MVVM + estado reactivo |
| Coroutines | 1.8+ | Operaciones asíncronas |
| UCrop | 2.2+ | Recorte de fotos en app |
| ZXing / QR Generate | - | Generar imágenes QR |
Arquitectura:
app/
├── data/
│ ├── local/
│ │ ├── database/ ← Room DB + SQLCipher
│ │ ├── dao/ ← DAOs de Room
│ │ └── entity/ ← Entidades Room
│ ├── repository/ ← Implementaciones de repositorios
│ └── preferences/ ← DataStore
├── domain/
│ ├── model/ ← Modelos de dominio (no Room)
│ ├── repository/ ← Interfaces de repositorios
│ └── usecase/ ← Casos de uso
├── presentation/
│ ├── home/ ← Dashboard principal (Composable)
│ ├── location/ ← Casas/Ubicaciones (Composable)
│ ├── room/ ← Habitaciones (Composable)
│ ├── box/ ← Cajas (Composable)
│ ├── item/ ← Items/Objetos (Composable)
│ ├── search/ ← Búsqueda global (Composable)
│ ├── settings/ ← Ajustes + PRO (Composable)
│ └── common/ ← Composables compartidos
├── di/ ← Módulos Hilt
└── util/ ← Utilidades
Modelo de datos completo:
// Jerarquía: Location → Room → Box → Item
// Una Box puede tener parentBoxId (caja dentro de caja)
Location: id, name, description, photoPath, createdAt
Room: id, locationId(FK), name, description, photoPath, createdAt
Box: id, roomId(FK), parentBoxId(FK nullable), name, description,
colorHex, coverPhotoPath, qrCodeData, createdAt
Item: id, boxId(FK), name, description, quantity, estimatedValue,
createdAt, updatedAt
ItemPhoto: id, itemId(FK), photoPath, sortOrder
Tag: id, name, colorHex
ItemTag: itemId(FK), tagId(FK) ← tabla pivote
Modelo Freemium:
// AppConfig.kt — ÚNICO lugar donde cambiar límites
object AppConfig {
const val FREE_ITEM_LIMIT = 500
const val FREE_PHOTOS_PER_ITEM = 3
const val FREE_MAX_LOCATIONS = Int.MAX_VALUE // ilimitado
const val FREE_MAX_ROOMS = Int.MAX_VALUE // ilimitado
const val FREE_MAX_BOXES = Int.MAX_VALUE // ilimitado
// PRO features
const val FEATURE_BACKUP = "feature_backup"
const val FEATURE_EXPORT = "feature_export"
const val FEATURE_UNLIMITED_PHOTOS = "feature_unlimited_photos"
const val FEATURE_ADVANCED_SEARCH = "feature_advanced_search"
// Play Store
const val PRO_PRODUCT_ID = "box_organizer_pro"
}
PASO PREVIO — RENOMBRAR PAQUETE DEL PROYECTO EXISTENTE
El proyecto ya existe con el paquete com.roundingmobile.boxorganizerinventory. Antes de empezar la Fase 1, renómbralo a com.roundingmobile.boi así:
-
En Android Studio, abre el proyecto existente.
-
En el panel Project (vista Android), expande
app > java. -
Haz clic derecho sobre
com.roundingmobile.boxorganizerinventory→ Refactor > Rename. -
Escribe
boiy confirma. Android Studio renombra el paquete en todos los archivos. -
Abre
app/build.gradle.ktsy verifica queapplicationId = "com.roundingmobile.boi". -
Abre
AndroidManifest.xmly verifica que no queda ninguna referencia al nombre antiguo. -
Haz Build > Clean Project seguido de Build > Rebuild Project.
-
Confirma que compila sin errores antes de empezar la Fase 1.
Nota: Si Android Studio no renombra correctamente, usa Edit > Find > Replace in Files para buscar
boxorganizerinventoryy reemplazar porboien todo el proyecto.
FASES DE DESARROLLO
FASE 1 — Setup del proyecto y dependencias
Objetivo: Proyecto Android nuevo con todas las dependencias configuradas y compilando.
Qué hacer:
-
Crea un nuevo proyecto Android en Android Studio con:
- Nombre:
BoxOrganizerInventory - Package:
com.roundingmobile.boi - Kotlin DSL (build.gradle.kts)
- Min SDK 27, Target SDK 36
- Empty Compose Activity
- Nombre:
-
Configura
libs.versions.tomlcon TODAS las versiones. -
Configura ambos
build.gradle.kts(proyecto y app) con todas las dependencias. -
Añade los permisos necesarios en
AndroidManifest.xml:<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.USE_BIOMETRIC" /> <uses-permission android:name="android.permission.INTERNET" /> -
Añade estas dependencias clave de Compose en
app/build.gradle.kts:val composeBom = platform("androidx.compose:compose-bom:2024.12.01") implementation(composeBom) implementation("androidx.compose.ui:ui") implementation("androidx.compose.material3:material3") implementation("androidx.compose.ui:ui-tooling-preview") implementation("androidx.activity:activity-compose:1.9.0") implementation("androidx.navigation:navigation-compose:2.7.7") implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0") debugImplementation("androidx.compose.ui:ui-tooling") -
Crea
AppConfig.ktcon todas las constantes freemium. -
Crea la clase
Applicationcon@HiltAndroidApp. -
Registra la Application en el Manifest.
─────────────────────────────────────────
✅ FIN DE FASE 1 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — sin errores
□ Run en emulador o dispositivo — app abre pantalla vacía sin crash
PRUEBAS MANUALES:
□ La app abre sin crash
□ En Logcat no hay errores rojos al arrancar
Escríbeme: "Fase 1 OK" o "Fase 1 ERROR: [descripción]"
─────────────────────────────────────────
FASE 2 — Base de datos Room + SQLCipher
Objetivo: BD cifrada funcionando con todas las entidades y DAOs.
Qué hacer:
-
Crea todas las entidades Room en
data/local/entity/:LocationEntity.ktRoomEntity.kt(usa nombreStorageRoompara no chocar conandroid.room)BoxEntity.ktItemEntity.ktItemPhotoEntity.ktTagEntity.ktItemTagEntity.kt(tabla pivote,@Entity(primaryKeys = ["itemId", "tagId"]))
-
Crea los DAOs en
data/local/dao/:LocationDao.kt— CRUD + Flow<List>StorageRoomDao.kt— CRUD + Flow por locationIdBoxDao.kt— CRUD + Flow por roomId + contar items totalesItemDao.kt— CRUD + Flow por boxId + contar total de items (para límite freemium)ItemPhotoDao.kt— CRUD por itemIdTagDao.kt— CRUD completoSearchDao.kt— FTS4 para búsqueda global
-
Crea
AppDatabase.ktcon SQLCipher:// IMPORTANTE: SQLCipher requiere SupportFactory // La clave se genera aleatoriamente y se guarda en EncryptedSharedPreferences val factory = SupportFactory(passphrase) Room.databaseBuilder(...) .openHelperFactory(factory) .build() -
Crea
DatabaseModule.kten Hilt con el provider de la BD. -
Crea una estrategia de migración vacía lista para usar:
// Siempre añade migraciones aquí antes de cambiar el schema val MIGRATION_1_2 = object : Migration(1, 2) { override fun migrate(db: SupportSQLiteDatabase) { // TODO: futuras migraciones aquí } }
─────────────────────────────────────────
✅ FIN DE FASE 2 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — sin errores
□ Room genera los archivos en build/generated — verifica que existen
PRUEBAS MANUALES:
□ La app abre sin crash
□ En Device Explorer (Android Studio) busca la BD cifrada en:
/data/data/com.roundingmobile.boi/databases/
Debe existir el archivo box_organizer.db
Escríbeme: "Fase 2 OK" o "Fase 2 ERROR: [descripción]"
─────────────────────────────────────────
FASE 3 — Hilt DI completo
Objetivo: Toda la inyección de dependencias configurada.
Qué hacer:
- Crea
DatabaseModule.kt— provee la BD y todos los DAOs. - Crea
RepositoryModule.kt— vincula interfaces con implementaciones. - Crea
PreferencesModule.kt— provee DataStore. - Anota
MainActivitycon@AndroidEntryPoint. - Verifica que el grafo de Hilt compila sin errores circulares.
─────────────────────────────────────────
✅ FIN DE FASE 3 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — Hilt genera código sin errores
□ Busca en Build output "Hilt" — no debe haber warnings críticos
PRUEBAS MANUALES:
□ La app abre sin crash
□ No hay NullPointerException en Logcat
Escríbeme: "Fase 3 OK" o "Fase 3 ERROR: [descripción]"
─────────────────────────────────────────
FASE 4 — Repository layer
Objetivo: Toda la lógica de datos encapsulada en repositorios.
Qué hacer:
-
Crea interfaces en
domain/repository/:LocationRepositoryStorageRoomRepositoryBoxRepositoryItemRepositoryTagRepository
-
Crea implementaciones en
data/repository/que:- Mapean Entity ↔ Domain model
- Exponen
Flow<>para datos reactivos - Usan
suspend funpara operaciones de escritura - Tienen manejo de errores con
Result<T>
-
Crea
ProStatusRepositoryque gestiona si el usuario es PRO o FREE y verifica el límite de 500 items.
─────────────────────────────────────────
✅ FIN DE FASE 4 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — sin errores
PRUEBAS MANUALES:
□ Escribe un test unitario simple que inserte una Location y la recupere
□ O usa el Database Inspector de Android Studio para verificar que la BD existe
Escríbeme: "Fase 4 OK" o "Fase 4 ERROR: [descripción]"
─────────────────────────────────────────
FASE 5 — Navegación + pantallas vacías
Objetivo: Toda la estructura de navegación compilando. Pantallas vacías pero accesibles.
Qué hacer:
-
Configura Navigation Compose con un
NavHostyNavControllerenMainActivity. -
Crea Composables de pantalla vacíos (con un Text con el nombre de la pantalla) para:
HomeScreen()LocationListScreen()LocationDetailScreen()AddEditLocationScreen()RoomListScreen()BoxListScreen()BoxDetailScreen()AddEditBoxScreen()ItemListScreen()ItemDetailScreen()AddEditItemScreen()SearchScreen()SettingsScreen()ProUpgradeScreen()QrScannerScreen()
-
Configura
MainActivityconScaffold+NavigationBarde Compose con 4 tabs:- 🏠 Inicio
- 🔍 Buscar
- 📷 Escanear QR
- ⚙️ Ajustes
-
IMPORTANTE: Cada tab del BottomNavigation debe navegar a su fragment real. No puede haber un tab que no haga nada.
─────────────────────────────────────────
✅ FIN DE FASE 5 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — sin errores
PRUEBAS MANUALES:
□ Abre la app — ves el NavigationBar de Compose con 4 tabs
□ Toca CADA tab — cada uno navega a una pantalla diferente (aunque vacía)
□ El botón Atrás funciona correctamente
□ Ningún tab se queda sin respuesta al tocarlo
Escríbeme: "Fase 5 OK" o "Fase 5 ERROR: [descripción]"
─────────────────────────────────────────
FASE 6 — Home Dashboard + Lista de Locations
Objetivo: Pantalla principal real con lista de casas/ubicaciones.
Qué hacer:
-
Implementa
HomeScreencon:- LazyColumn de Locations
- Card por location con: foto (o placeholder), nombre, número de cajas, número de items
- FAB (+) para añadir nueva location
- Estado vacío con ilustración cuando no hay locations
- Contador
X / 500 itemsvisible siempre en la toolbar (FREE) o∞ items(PRO)
-
Implementa
HomeViewModelcon:StateFlow<List<Location>>para la listaStateFlow<Int>para el conteo total de itemsStateFlow<Boolean>para saber si es PRO
-
El FAB (+) navega a
AddEditLocationScreen. -
Tocar una Location navega a
LocationDetailScreenpasando el ID.
─────────────────────────────────────────
✅ FIN DE FASE 6 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
COMPILACIÓN:
□ Build > Make Project — sin errores
PRUEBAS MANUALES:
□ Pantalla Home se ve con LazyColumn vacío y estado vacío bonito
□ El contador "0 / 500 items" aparece en la toolbar
□ El FAB (+) responde al toque (navega, aunque el formulario esté vacío aún)
□ No hay botones visibles que no hagan nada
Escríbeme: "Fase 6 OK" o "Fase 6 ERROR: [descripción]"
─────────────────────────────────────────
FASE 7 — CRUD completo de Locations
Objetivo: Crear, ver, editar y eliminar ubicaciones funciona al 100%.
Qué hacer:
-
AddEditLocationScreen:- Campo nombre (obligatorio, validación en tiempo real)
- Campo descripción (opcional)
- Botón foto: abre cámara con CameraX o galería (el usuario elige)
- Vista previa de foto seleccionada con botón para eliminarla
- Botón Guardar: habilitado solo cuando el nombre no está vacío
- Botón Cancelar: confirma si hay cambios sin guardar
- Modo edición: carga datos existentes automáticamente
-
LocationDetailScreen:- Muestra foto, nombre, descripción
- Lista de rooms de esta location
- FAB para añadir room
- Menú (⋮) con opciones: Editar, Eliminar
- Al eliminar: diálogo de confirmación "¿Eliminar esta ubicación y TODO su contenido?"
-
Manejo de fotos:
- Guarda en almacenamiento interno de la app (no externo)
- Comprime a máximo 1080px y calidad 85%
- Elimina foto anterior al reemplazarla
─────────────────────────────────────────
✅ FIN DE FASE 7 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES — HAZ CADA UNA:
□ Crea una Location "Mi Casa" con descripción y foto
□ Verifica que aparece en el Home con la foto
□ El contador sigue en "0 / 500 items"
□ Edita la Location — cambia el nombre — guarda — verifica el cambio
□ Crea una segunda Location "Trastero"
□ Elimina "Trastero" — confirma el diálogo — desaparece de la lista
□ Intenta guardar una Location sin nombre — el botón Guardar está deshabilitado
Escríbeme: "Fase 7 OK" o "Fase 7 ERROR: [descripción]"
─────────────────────────────────────────
FASE 8 — CRUD completo de Rooms (Habitaciones)
Objetivo: Gestión completa de habitaciones dentro de una ubicación.
Igual que Fase 7 pero para Rooms. Implementa:
- Lista de rooms en
LocationDetailScreen(ya existe el contenedor) AddEditRoomScreencon nombre, descripción, fotoRoomDetailScreencon lista de cajas + FAB + menú editar/eliminar- Navegación bidireccional correcta (back stack)
─────────────────────────────────────────
✅ FIN DE FASE 8 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Entra en "Mi Casa" → crea habitación "Salón"
□ Crea "Cocina" y "Ático"
□ Edita "Salón" → renómbralo "Sala de estar"
□ Elimina "Cocina" con confirmación
□ Back desde RoomDetail vuelve a LocationDetail correctamente
Escríbeme: "Fase 8 OK" o "Fase 8 ERROR: [descripción]"
─────────────────────────────────────────
FASE 9 — CRUD completo de Boxes + QR
Objetivo: Gestión de cajas con generación de QR único por caja.
Qué hacer:
-
AddEditBoxScreen:- Nombre, descripción, selector de color (8 colores predefinidos)
- Foto de portada de la caja
- Al CREAR: genera automáticamente un QR único (
UUID.randomUUID().toString()) - El QR se genera UNA sola vez y nunca cambia
-
BoxDetailScreen:- Muestra foto portada, nombre, color
- Botón "Ver QR" → abre diálogo con la imagen QR grande + botón "Imprimir etiqueta PDF"
- Lista de items de la caja
- FAB para añadir item
- Indicador de items:
X itemsoX / 3 fotosno — simplemente cuenta de items
-
Generar imagen QR:
// Usa ZXing para generar Bitmap del QR val writer = QRCodeWriter() val bitMatrix = writer.encode(qrData, BarcodeFormat.QR_CODE, 512, 512) // Convierte BitMatrix a Bitmap y guárdalo -
"Imprimir etiqueta PDF":
- Genera un PDF simple con: nombre de caja, imagen QR, nombre de la room y location
- Abre el PDF con Android ShareSheet para imprimir o compartir
─────────────────────────────────────────
✅ FIN DE FASE 9 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ En "Sala de estar" → crea caja "Libros" con color azul y foto
□ Entra en la caja → botón "Ver QR" → aparece imagen QR
□ El QR se ve grande y legible en el diálogo
□ Botón "Imprimir etiqueta" → genera PDF → se abre ShareSheet
□ Crea caja "Herramientas" — tiene QR diferente al de "Libros"
□ El FAB para añadir items existe y responde (aunque el form venga después)
Escríbeme: "Fase 9 OK" o "Fase 9 ERROR: [descripción]"
─────────────────────────────────────────
FASE 10 — CRUD completo de Items + Fotos (CameraX)
Objetivo: Añadir objetos a cajas con fotos. El core de la app.
Qué hacer:
-
AddEditItemScreen:- Nombre (obligatorio), descripción, cantidad (default 1), valor estimado (opcional)
- Sección de fotos: grid de fotos añadidas + botón "+" para añadir foto
- Botón "+" foto: muestra bottom sheet con opciones "Cámara" / "Galería"
- Límite FREE: máximo 3 fotos por item — si es FREE y ya tiene 3, el botón "+" muestra un SnackBar: "Actualiza a PRO para fotos ilimitadas"
- Recorte con UCrop (librería externa, se lanza como Activity desde Compose con rememberLauncherForActivityResult)
- Reordenación de fotos con drag & drop (ReorderableList o LazyColumn con detectDragGestures)
- Validación: nombre obligatorio, cantidad mínimo 1
-
GATE FREEMIUM — MUY IMPORTANTE:
// Antes de guardar un item nuevo, verifica el límite val totalItems = itemRepository.getTotalItemCount() if (!proStatusRepository.isPro() && totalItems >= AppConfig.FREE_ITEM_LIMIT) { // NO guardes el item // Navega a ProUpgradeFragment con animación showUpgradeDialog() return } -
ItemDetailScreen:- HorizontalPager (Compose) con las fotos
- Todos los datos del item
- Chips de tags (implementación básica ahora, tags completos en fase 12)
- Menú editar / eliminar
-
Gestión de fotos:
- Guarda en
filesDir/items/{itemId}/photo_{order}.jpg - Comprime a 1080px máx, calidad 80
- Al eliminar item, borra todas sus fotos del disco
- Guarda en
─────────────────────────────────────────
✅ FIN DE FASE 10 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ En caja "Libros" → añade item "El Señor de los Anillos"
□ Añade 2 fotos (cámara o galería) — se ven en el grid
□ Recorta una foto con UCrop — se guarda recortada
□ Guarda el item — aparece en la lista de la caja
□ El contador del Home cambia a "1 / 500 items"
□ Edita el item — cambia la cantidad — guarda — verifica
□ Añade 10 items más para probar el contador
□ Verifica que el contador del Home se actualiza en tiempo real
□ Intenta añadir una 4ª foto a un item — aparece mensaje de upgrade
Escríbeme: "Fase 10 OK" o "Fase 10 ERROR: [descripción]"
─────────────────────────────────────────
FASE 11 — ML Kit Auto-tags
Objetivo: Al hacer foto de un item, la app sugiere tags automáticamente.
Qué hacer:
-
Después de capturar/seleccionar foto en
AddEditItemScreen, lanza ML Kit Image Labeling:val labeler = ImageLabeling.getClient(ImageLabelerOptions.DEFAULT_OPTIONS) labeler.process(InputImage.fromFilePath(context, photoUri)) .addOnSuccessListener { labels -> // Filtra labels con confidence > 0.75 // Mapea a strings en español (crea un diccionario de traducción) // Muestra chips sugeridos que el usuario puede tocar para aceptar } -
Muestra los tags sugeridos como chips con checkmark: "✓ Libro", "✓ Electrónica"...
-
El usuario toca los chips para aceptarlos o rechazarlos.
-
Los chips aceptados se convierten en tags del item.
-
Diccionario mínimo de traducción EN→ES para las etiquetas más comunes.
-
Si ML Kit no detecta nada con suficiente confianza, no muestra nada (sin error visible).
─────────────────────────────────────────
✅ FIN DE FASE 11 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Añade un item y haz foto a un libro — aparecen chips sugeridos
□ Acepta un chip — se añade como tag al item
□ Rechaza un chip — no se añade
□ Si ML Kit no detecta nada, no aparece ningún error
□ Los tags aceptados se ven en el detalle del item
Escríbeme: "Fase 11 OK" o "Fase 11 ERROR: [descripción]"
─────────────────────────────────────────
FASE 12 — Búsqueda global (Room FTS4)
Objetivo: Búsqueda instantánea en toda la app.
Qué hacer:
-
Añade tabla FTS4 en Room para búsqueda:
@Fts4(contentEntity = ItemEntity::class) @Entity(tableName = "items_fts") data class ItemFtsEntity( val name: String, val description: String, val tags: String ) -
SearchScreen:- TextField de Compose como buscador con búsqueda en tiempo real (debounce 300ms)
- Resultados agrupados: primero Items, luego Boxes, luego Locations
- Cada resultado muestra: foto miniatura, nombre, ubicación completa (Casa > Hab > Caja)
- Tocar resultado navega directamente al item/caja/location
- Estado vacío con texto "Busca objetos, cajas o ubicaciones"
- Estado sin resultados con texto "Sin resultados para '[query]'"
-
Búsqueda básica FREE: busca solo por nombre y descripción.
-
Búsqueda avanzada PRO: filtros adicionales por tag, valor, fecha. (Implementa la UI pero con gate PRO.)
─────────────────────────────────────────
✅ FIN DE FASE 12 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Tab Buscar → escribe "señor" → aparece "El Señor de los Anillos"
□ El resultado muestra en qué caja y habitación está
□ Toca el resultado → navega directamente al item
□ Busca algo que no existe → mensaje "Sin resultados"
□ Borra el texto → vuelve al estado inicial
□ La búsqueda es instantánea (sin retraso visible)
Escríbeme: "Fase 12 OK" o "Fase 12 ERROR: [descripción]"
─────────────────────────────────────────
FASE 13 — QR Scanner
Objetivo: Escanear QR de una caja y navegar directamente a ella.
Qué hacer:
-
QrScannerScreen(tab 3 del BottomNav):- Preview de cámara en tiempo real con ML Kit Barcode Scanner
- Marco de escaneo animado (línea roja que baja y sube)
- Al detectar QR: busca en BD el box con ese qrCodeData
- Si encuentra: navega a
BoxDetailScreencon animación - Si NO encuentra: muestra Snackbar "Este QR no pertenece a ninguna caja"
- Botón X para cerrar el scanner
-
Pide permiso de cámara si no está concedido (con explicación clara al usuario).
─────────────────────────────────────────
✅ FIN DE FASE 13 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Tab Escanear → se abre cámara con el marco de escaneo
□ Imprime o muestra en pantalla el QR de la caja "Libros"
□ Escanéalo → navega directamente a la caja "Libros"
□ Escanea un QR cualquiera de internet → "Este QR no pertenece a ninguna caja"
□ El botón X cierra el scanner y vuelve al Home
Escríbeme: "Fase 13 OK" o "Fase 13 ERROR: [descripción]"
─────────────────────────────────────────
FASE 14 — Freemium gates completos
Objetivo: El límite de 500 items funciona perfectamente en todos los puntos.
Qué hacer:
-
Contador permanente en toolbar del Home:
- FREE:
"487 / 500 items"en color normal →"490 / 500 items"en naranja →"500 / 500 items"en rojo - PRO:
"1.243 items ∞"
- FREE:
-
Al intentar crear item número 501:
- NO guarda el item
- Abre
ProUpgradeScreencon animación de abajo hacia arriba - El usuario puede cerrar y volver sin perder lo que estaba haciendo
-
Barra de progreso sutil en el Home (solo FREE):
[████████░░] 487/500 -
ProUpgradeScreen:- Lista clara de qué incluye PRO
- Precio (obtenido de Play Billing en la siguiente fase — por ahora muestra "Cargando...")
- Botón "Actualizar a PRO" (conectado en fase 15)
- Botón "Quizás después" — cierra el fragment
─────────────────────────────────────────
✅ FIN DE FASE 14 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ El contador de items se ve siempre en el Home
□ Añade items hasta llegar a 498 → el contador se vuelve naranja
□ Llega a 500 → se vuelve rojo
□ Intenta crear el item 501 → abre pantalla de upgrade
□ Cierra el upgrade → puedes seguir navegando normalmente
□ La pantalla ProUpgrade muestra la lista de features PRO correctamente
Escríbeme: "Fase 14 OK" o "Fase 14 ERROR: [descripción]"
─────────────────────────────────────────
FASE 15 — Google Play Billing v6
Objetivo: Compra real de PRO funcionando.
Qué hacer:
-
Implementa
BillingRepositorycon Play Billing v6:- Conecta al BillingClient al iniciar la app
- Obtiene detalles del producto
box_organizer_pro - Verifica compras pendientes al arrancar
- Gestiona el flujo completo: iniciar compra → confirmar → guardar estado
-
Guarda el estado PRO en DataStore (persiste entre sesiones):
// Si la compra se verifica → isProUser = true → se guarda en DataStore -
En
ProUpgradeScreen: muestra el precio real obtenido de Play Billing. -
El botón "Actualizar a PRO" lanza el flujo de compra de Google Play.
-
IMPORTANTE para pruebas: Configura un tester interno en Google Play Console para poder probar sin pagar.
-
Tras compra exitosa: el contador del Home cambia a
∞, todos los gates PRO se desbloquean.
─────────────────────────────────────────
✅ FIN DE FASE 15 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES (requiere configuración en Play Console):
□ La pantalla PRO muestra el precio real del producto
□ El botón "Actualizar a PRO" abre el diálogo de compra de Google
□ Con cuenta de tester: completa la compra
□ Tras compra: el contador del Home muestra "∞"
□ Puedes añadir el item 501 sin gate
□ Cierra y reabre la app — sigues siendo PRO
Escríbeme: "Fase 15 OK" o "Fase 15 ERROR: [descripción]"
─────────────────────────────────────────
FASE 16 — Export Excel + PDF (PRO)
Objetivo: Exportar inventario completo a Excel y PDF.
Qué hacer:
-
En
SettingsScreen(o menú en Home), botones PRO:- "Exportar a Excel" — con ícono de candado si es FREE
- "Exportar a PDF" — con ícono de candado si es FREE
- Si es FREE y toca: navega a
ProUpgradeScreen
-
Export Excel con Apache POI:
- Una hoja por Location
- Columnas: Habitación, Caja, Item, Descripción, Cantidad, Valor, Tags
- Estilo básico: cabecera en negrita, bordes
-
Export PDF:
- Una sección por Location → Room → Box
- Items listados con foto miniatura (máx 100px), nombre, descripción
- Genera con
PdfDocumentde Android (no requiere librería externa)
-
Tras generar: abre Android ShareSheet para guardar o compartir
─────────────────────────────────────────
✅ FIN DE FASE 16 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES (requiere ser PRO o simular):
□ Botón Export Excel → genera archivo .xlsx → ShareSheet se abre
□ Abre el Excel en Google Sheets — los datos se ven correctamente
□ Botón Export PDF → genera PDF → ShareSheet se abre
□ El PDF muestra fotos e items correctamente
□ En modo FREE los botones tienen el candado y abren el upgrade
Escríbeme: "Fase 16 OK" o "Fase 16 ERROR: [descripción]"
─────────────────────────────────────────
FASE 17 — Backup Google Drive cifrado (PRO)
Objetivo: Backup automático cifrado en Google Drive con restauración de 1 tap.
Qué hacer:
-
Flujo de backup:
- Serializa toda la BD a JSON (locations, rooms, boxes, items, fotos)
- Cifra el JSON con AES-256 usando una clave derivada de la cuenta Google del usuario
- Sube a una carpeta de Drive llamada "BoxOrganizerBackup"
- Guarda timestamp del último backup en DataStore
-
WorkManager para backup automático:
- Se ejecuta cada 24h si hay WiFi y batería > 20%
- Notificación silenciosa de "Backup completado"
- Si falla: notificación con botón "Reintentar"
-
En
SettingsScreen(PRO):- "Último backup: hace 2 horas" o "Nunca"
- Botón "Hacer backup ahora"
- Botón "Restaurar desde backup" → lista los backups disponibles en Drive
-
Restauración:
- Muestra lista de backups con fecha
- Confirmación: "¿Restaurar? Se reemplazarán TODOS los datos actuales"
- Descifra, deserializa, reimporta a Room
─────────────────────────────────────────
✅ FIN DE FASE 17 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Conecta cuenta Google → botón "Backup ahora" → se sube a Drive
□ Abre Google Drive en el móvil → carpeta BoxOrganizerBackup → existe el archivo
□ El archivo NO es legible directamente (está cifrado)
□ "Último backup" muestra la fecha correcta en Settings
□ Borra todos los datos manualmente de la app
□ Restaura desde backup → todos los datos vuelven
□ El backup automático aparece en WorkManager (App Inspection en Android Studio)
Escríbeme: "Fase 17 OK" o "Fase 17 ERROR: [descripción]"
─────────────────────────────────────────
FASE 18 — Export/Import archivo (Compartir familiar v1)
Objetivo: Exportar el inventario completo a un archivo para importar en otro dispositivo.
Qué hacer:
-
Exportar:
- Genera un archivo
.boxorg(ZIP renombrado) con el JSON + todas las fotos comprimidas - Abre ShareSheet: el usuario puede enviarlo por WhatsApp, email, Drive, etc.
- Genera un archivo
-
Importar:
- En Settings: botón "Importar inventario"
- Abre file picker para seleccionar archivo
.boxorg - Muestra diálogo: "¿Importar? Puedes elegir: Fusionar con datos actuales / Reemplazar todo"
- Importa y muestra progreso
-
Info clara al usuario: "Los datos se importarán tal como fueron exportados. Las fotos se incluyen en el archivo."
─────────────────────────────────────────
✅ FIN DE FASE 18 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Settings → "Exportar inventario" → genera archivo .boxorg
□ El archivo tiene un tamaño razonable (incluye fotos comprimidas)
□ Envíate el archivo por email o Drive
□ En el mismo u otro dispositivo: "Importar inventario" → selecciona el archivo
□ Elige "Fusionar" → los datos se añaden sin borrar los existentes
□ Elige "Reemplazar" → los datos se sustituyen completamente
Escríbeme: "Fase 18 OK" o "Fase 18 ERROR: [descripción]"
─────────────────────────────────────────
FASE 19 — Biometría / PIN
Objetivo: La app se bloquea y desbloquea con huella, face ID o PIN.
Qué hacer:
-
Primera vez que abre la app: no pide biometría (opt-in en Settings).
-
En Settings: toggle "Proteger app con biometría/PIN".
-
Al activar: pide configurar PIN de 4-6 dígitos como respaldo.
-
Cuando está activado: al abrir la app (o volver de background tras 60 segundos) pide autenticación.
-
Usa
BiometricPromptcon fallback a PIN propio:val promptInfo = BiometricPrompt.PromptInfo.Builder() .setTitle("Desbloquear Box Organizer") .setSubtitle("Usa tu huella o PIN") .setNegativeButtonText("Usar PIN") .build() -
La clave de SQLCipher se deriva del PIN + salt almacenado en EncryptedSharedPreferences.
─────────────────────────────────────────
✅ FIN DE FASE 19 — VERIFICACIÓN OBLIGATORIA
─────────────────────────────────────────
PRUEBAS MANUALES:
□ Settings → activa protección → configura PIN "1234"
□ Pon la app en background → espera 60s → vuelve → pide autenticación
□ Usa huella → desbloquea
□ Cancela huella → botón "Usar PIN" → introduce PIN → desbloquea
□ PIN incorrecto → mensaje de error, no desbloquea
□ Desactiva protección desde Settings → ya no pide autenticación
Escríbeme: "Fase 19 OK" o "Fase 19 ERROR: [descripción]"
─────────────────────────────────────────
FASE 20 — Pulido final: UI, Widget Android, Dark Mode
Objetivo: App pulida, con widget de pantalla de inicio y modo oscuro perfecto.
Qué hacer:
-
Widget de Android (el más importante de esta fase):
- Widget 4x1: buscador rápido de items
- Al escribir en el widget → abre la app directamente en
SearchScreencon el texto - Widget 2x2: muestra los últimos 4 items añadidos
-
Modo oscuro: verifica TODAS las pantallas en modo oscuro. Corrige cualquier color hardcodeado que no se adapte.
-
Animaciones de transición entre pantallas (Material Motion):
- Shared Element Transition en las fotos
- Fade + slide en navegación normal
-
Estados de carga: cada pantalla que carga datos de BD muestra un skeleton o spinner mientras carga.
-
Manejo de errores visible: si algo falla (BD corrupta, Drive desconectado), el usuario ve un mensaje claro — nunca una pantalla en blanco o un crash.
-
Revisión final de accesibilidad:
- Todos los ImageView tienen
contentDescription - Touch targets mínimo 48dp
- Contraste de color correcto en modo claro y oscuro
- Todos los ImageView tienen
─────────────────────────────────────────
✅ FIN DE FASE 20 — VERIFICACIÓN FINAL COMPLETA
─────────────────────────────────────────
PRUEBAS MANUALES COMPLETAS:
□ Widget en pantalla de inicio — búsqueda abre la app
□ Activa modo oscuro del sistema — toda la app se ve bien
□ Navega por TODAS las pantallas en modo oscuro — sin colores rotos
□ El widget de últimos items muestra datos reales
□ Transiciones suaves entre pantallas
□ Sin crashes en ningún flujo normal de uso
□ TalkBack activado — todos los elementos son accesibles
🎉 ¡FELICIDADES! La app está completa.
Siguiente paso: preparar para subir a Google Play (firma, screenshots, descripción).
Escríbeme: "Fase 20 OK - APP COMPLETA" 🚀
─────────────────────────────────────────
NOTAS FINALES PARA CLAUDE CODE
- Nunca generes código que no compile. Si tienes dudas sobre una API, usa la más simple que funcione.
- Cada archivo .kt que crees debe ser completo — sin imports faltantes, sin referencias a clases no creadas aún.
- Si el programador reporta un error, arréglalo completamente antes de continuar con la siguiente fase.
- El programador es el QA — si dice que algo no funciona, es verdad. No lo cuestiones, arréglalo.
- Documenta el "por qué" en comentarios, no solo el "qué".
¡Mucho ánimo con el proyecto! 📦