rmt-box.organizer.inventory/box-organizer-prompt-es.md
Quiver d1d64c3854 Complete Box Organizer Inventory app implementation
- 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>
2026-04-05 16:31:47 +02:00

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:

  1. Completa UNA fase entera antes de pasar a la siguiente.
  2. Al final de cada fase, PARA y pregunta al programador que compile y pruebe.
  3. 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 // TODO solo en lógica interna, nunca en UI.
  4. Si algo no compila, arréglalo antes de continuar. No des por bueno nada que dé error.
  5. 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.
  6. Sobre-explica todo en comentarios del código. No asumas que el programador sabe por qué haces algo.
  7. 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í:

  1. En Android Studio, abre el proyecto existente.

  2. En el panel Project (vista Android), expande app > java.

  3. Haz clic derecho sobre com.roundingmobile.boxorganizerinventoryRefactor > Rename.

  4. Escribe boi y confirma. Android Studio renombra el paquete en todos los archivos.

  5. Abre app/build.gradle.kts y verifica que applicationId = "com.roundingmobile.boi".

  6. Abre AndroidManifest.xml y verifica que no queda ninguna referencia al nombre antiguo.

  7. Haz Build > Clean Project seguido de Build > Rebuild Project.

  8. 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 boxorganizerinventory y reemplazar por boi en 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:

  1. 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
  2. Configura libs.versions.toml con TODAS las versiones.

  3. Configura ambos build.gradle.kts (proyecto y app) con todas las dependencias.

  4. 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" />
    
  5. 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")
    
  6. Crea AppConfig.kt con todas las constantes freemium.

  7. Crea la clase Application con @HiltAndroidApp.

  8. 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:

  1. Crea todas las entidades Room en data/local/entity/:

    • LocationEntity.kt
    • RoomEntity.kt (usa nombre StorageRoom para no chocar con android.room)
    • BoxEntity.kt
    • ItemEntity.kt
    • ItemPhotoEntity.kt
    • TagEntity.kt
    • ItemTagEntity.kt (tabla pivote, @Entity(primaryKeys = ["itemId", "tagId"]))
  2. Crea los DAOs en data/local/dao/:

    • LocationDao.kt — CRUD + Flow<List>
    • StorageRoomDao.kt — CRUD + Flow por locationId
    • BoxDao.kt — CRUD + Flow por roomId + contar items totales
    • ItemDao.kt — CRUD + Flow por boxId + contar total de items (para límite freemium)
    • ItemPhotoDao.kt — CRUD por itemId
    • TagDao.kt — CRUD completo
    • SearchDao.kt — FTS4 para búsqueda global
  3. Crea AppDatabase.kt con SQLCipher:

    // IMPORTANTE: SQLCipher requiere SupportFactory
    // La clave se genera aleatoriamente y se guarda en EncryptedSharedPreferences
    val factory = SupportFactory(passphrase)
    Room.databaseBuilder(...)
        .openHelperFactory(factory)
        .build()
    
  4. Crea DatabaseModule.kt en Hilt con el provider de la BD.

  5. 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:

  1. Crea DatabaseModule.kt — provee la BD y todos los DAOs.
  2. Crea RepositoryModule.kt — vincula interfaces con implementaciones.
  3. Crea PreferencesModule.kt — provee DataStore.
  4. Anota MainActivity con @AndroidEntryPoint.
  5. 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:

  1. Crea interfaces en domain/repository/:

    • LocationRepository
    • StorageRoomRepository
    • BoxRepository
    • ItemRepository
    • TagRepository
  2. Crea implementaciones en data/repository/ que:

    • Mapean Entity ↔ Domain model
    • Exponen Flow<> para datos reactivos
    • Usan suspend fun para operaciones de escritura
    • Tienen manejo de errores con Result<T>
  3. Crea ProStatusRepository que 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:

  1. Configura Navigation Compose con un NavHost y NavController en MainActivity.

  2. 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()
  3. Configura MainActivity con Scaffold + NavigationBar de Compose con 4 tabs:

    • 🏠 Inicio
    • 🔍 Buscar
    • 📷 Escanear QR
    • ⚙️ Ajustes
  4. 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:

  1. Implementa HomeScreen con:

    • 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 items visible siempre en la toolbar (FREE) o ∞ items (PRO)
  2. Implementa HomeViewModel con:

    • StateFlow<List<Location>> para la lista
    • StateFlow<Int> para el conteo total de items
    • StateFlow<Boolean> para saber si es PRO
  3. El FAB (+) navega a AddEditLocationScreen.

  4. Tocar una Location navega a LocationDetailScreen pasando 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:

  1. 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
  2. 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?"
  3. 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)
  • AddEditRoomScreen con nombre, descripción, foto
  • RoomDetailScreen con 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:

  1. 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
  2. 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 items o X / 3 fotos no — simplemente cuenta de items
  3. 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
    
  4. "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:

  1. 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
  2. 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
    }
    
  3. 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
  4. 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
─────────────────────────────────────────
✅ 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:

  1. 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
        }
    
  2. Muestra los tags sugeridos como chips con checkmark: "✓ Libro", "✓ Electrónica"...

  3. El usuario toca los chips para aceptarlos o rechazarlos.

  4. Los chips aceptados se convierten en tags del item.

  5. Diccionario mínimo de traducción EN→ES para las etiquetas más comunes.

  6. 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:

  1. 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
    )
    
  2. 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]'"
  3. Búsqueda básica FREE: busca solo por nombre y descripción.

  4. 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:

  1. 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 BoxDetailScreen con animación
    • Si NO encuentra: muestra Snackbar "Este QR no pertenece a ninguna caja"
    • Botón X para cerrar el scanner
  2. 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:

  1. 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 ∞"
  2. Al intentar crear item número 501:

    • NO guarda el item
    • Abre ProUpgradeScreen con animación de abajo hacia arriba
    • El usuario puede cerrar y volver sin perder lo que estaba haciendo
  3. Barra de progreso sutil en el Home (solo FREE): [████████░░] 487/500

  4. 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:

  1. Implementa BillingRepository con 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
  2. Guarda el estado PRO en DataStore (persiste entre sesiones):

    // Si la compra se verifica → isProUser = true → se guarda en DataStore
    
  3. En ProUpgradeScreen: muestra el precio real obtenido de Play Billing.

  4. El botón "Actualizar a PRO" lanza el flujo de compra de Google Play.

  5. IMPORTANTE para pruebas: Configura un tester interno en Google Play Console para poder probar sin pagar.

  6. 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:

  1. 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
  2. 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
  3. Export PDF:

    • Una sección por Location → Room → Box
    • Items listados con foto miniatura (máx 100px), nombre, descripción
    • Genera con PdfDocument de Android (no requiere librería externa)
  4. 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:

  1. 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
  2. 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"
  3. 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
  4. 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:

  1. 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.
  2. 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
  3. 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:

  1. Primera vez que abre la app: no pide biometría (opt-in en Settings).

  2. En Settings: toggle "Proteger app con biometría/PIN".

  3. Al activar: pide configurar PIN de 4-6 dígitos como respaldo.

  4. Cuando está activado: al abrir la app (o volver de background tras 60 segundos) pide autenticación.

  5. Usa BiometricPrompt con fallback a PIN propio:

    val promptInfo = BiometricPrompt.PromptInfo.Builder()
        .setTitle("Desbloquear Box Organizer")
        .setSubtitle("Usa tu huella o PIN")
        .setNegativeButtonText("Usar PIN")
        .build()
    
  6. 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:

  1. 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 SearchScreen con el texto
    • Widget 2x2: muestra los últimos 4 items añadidos
  2. Modo oscuro: verifica TODAS las pantallas en modo oscuro. Corrige cualquier color hardcodeado que no se adapte.

  3. Animaciones de transición entre pantallas (Material Motion):

    • Shared Element Transition en las fotos
    • Fade + slide en navegación normal
  4. Estados de carga: cada pantalla que carga datos de BD muestra un skeleton o spinner mientras carga.

  5. 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.

  6. Revisión final de accesibilidad:

    • Todos los ImageView tienen contentDescription
    • Touch targets mínimo 48dp
    • Contraste de color correcto en modo claro y oscuro
─────────────────────────────────────────
✅ 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! 📦