# Packet - Especificación Técnica Completa ## 1. Visión General Packet permite: - Capturar contenido multimedia (fotos, audio, video, archivos) - Etiquetarlo con referencias de bibliotecas externas - Empaquetarlo en contenedores con hash único - Enviarlo a backends configurables ### Principios | Principio | Descripción | |-----------|-------------| | Zero-retention | Los archivos nunca se almacenan en disco, solo en RAM hasta envío | | Hash-first | Todo contenedor tiene hash único generado localmente | | Contenedor cerrado | Una vez montado, no se inspecciona. Solo enviar o desmontar | | App tonta | No valida coherencia de etiquetas, solo formato. El backend decide | | Offline-capable | Captura sin conexión, cola de reintentos cuando hay red | --- ## 2. Stack Tecnológico | Componente | Tecnología | |------------|------------| | Framework | Flutter 3.x | | Lenguaje | Dart | | Estado | flutter_bloc | | DB Local | sqflite | | HTTP | dio | | Crypto | crypto (SHA-256) | | Media | image_picker, record | | Location | geolocator | | Permisos | permission_handler | --- ## 3. Estructura de Carpetas ``` packet/ ├── android/ ├── ios/ ├── lib/ │ ├── core/ │ │ ├── constants/ │ │ │ └── app_constants.dart │ │ ├── errors/ │ │ │ └── exceptions.dart │ │ ├── utils/ │ │ │ ├── hash_utils.dart │ │ │ └── retry_utils.dart │ │ └── theme/ │ │ └── app_theme.dart │ │ │ ├── data/ │ │ ├── datasources/ │ │ │ ├── local_database.dart │ │ │ ├── backend_api.dart │ │ │ └── biblioteca_api.dart │ │ ├── models/ │ │ │ ├── destino_model.dart │ │ │ ├── contenedor_model.dart │ │ │ ├── etiqueta_model.dart │ │ │ └── pack_model.dart │ │ └── repositories/ │ │ ├── contenedor_repository.dart │ │ ├── etiqueta_repository.dart │ │ └── config_repository.dart │ │ │ ├── domain/ │ │ └── entities/ │ │ ├── contenedor.dart │ │ ├── etiqueta.dart │ │ ├── pack.dart │ │ ├── destino.dart │ │ └── archivo_adjunto.dart │ │ │ ├── presentation/ │ │ ├── bloc/ │ │ │ ├── captura/ │ │ │ ├── etiquetas/ │ │ │ ├── packs/ │ │ │ ├── pendientes/ │ │ │ └── config/ │ │ ├── pages/ │ │ │ ├── captura_page.dart │ │ │ ├── etiquetas_page.dart │ │ │ ├── packs_page.dart │ │ │ ├── pendientes_page.dart │ │ │ └── config_page.dart │ │ ├── widgets/ │ │ │ ├── audio_recorder.dart │ │ │ ├── destino_selector.dart │ │ │ ├── etiqueta_grid.dart │ │ │ └── contenedor_card.dart │ │ └── app.dart │ │ │ └── main.dart │ ├── test/ ├── pubspec.yaml └── README.md ``` --- ## 4. Pantallas ### Navegación ``` ┌─────────────────────────────────────────┐ │ [Mi DECK ▼] [CORP] [+Ext] │ ← Destinos (superior) ├─────────────────────────────────────────┤ │ │ │ CONTENIDO PANTALLA │ │ │ ├─────────────────────────────────────────┤ │ 📷 🏷️ 📦 ⏳ ⚙️ │ ← Navegación (inferior) │ Captura Tags Packs Pend. Config │ └─────────────────────────────────────────┘ ``` ### 4.1 Captura Montar contenedor con contenido multimedia. Campos: - Título (string, opcional) - Descripción (string, opcional, se procesa en backend) - Identificador (string 64 chars, auto-generado SHA-256) - Archivos (array, opcional) - GPS (lat/long, opcional) - Etiquetas (array de hashes, seleccionadas en pestaña Etiquetas) Botones de captura: - 📷 Foto (cámara) - 🎤 Audio (con contador de tiempo) - 🎥 Video - 📎 Archivo - 📍 GPS El hash se genera al abrir la app o tras cada envío. Se puede enviar contenedor vacío (solo hash). Botones de copiar y regenerar hash visibles. ### 4.2 Etiquetas Seleccionar etiquetas de bibliotecas y añadir hashes externos. Secciones: 1. Grid HST - Iconos de biblioteca HST (se oscurecen al seleccionar) 2. Grid DECK - Iconos de biblioteca DECK 3. Campo hash - Pegar URL o hash externo 4. Seleccionadas - Lista de etiquetas actuales con botón eliminar Validación de hash externo: 1. Usuario pega texto 2. App busca patrón: [a-f0-9]{64} 3. Por cada match → check verde inmediato 4. Background: consulta /api/tags para resolver nombre 5. Si encuentra → muestra nombre 6. Si no → muestra hash truncado ### 4.3 Packs Conjuntos predefinidos de etiquetas creados localmente. - 1 tap → aplica todas las etiquetas del pack - Creación: nombre + etiquetas actualmente seleccionadas - Almacenados en SQLite local ### 4.4 Pendientes Cola de contenedores fallidos. Límite: 20 contenedores máximo. App se bloquea si se llena. Por cada contenedor muestra: - Hash (truncado) - Título (o "sin título") - Número de archivos - Intentos realizados (máx 20) - Último intento (hora) - Estado (error / reintentando) Acciones: - Reintentar → Intenta enviar de nuevo - Recuperar → Extrae archivos al almacenamiento del dispositivo y elimina contenedor ### 4.5 Config URLs, llaves y bibliotecas. Por cada destino: - Nombre (identificador visual) - URL Backend (endpoint de envío) - Llave (hash de 64 chars para autenticación) Bibliotecas de iconos: - URL (dominio) - Endpoint (ruta del índice, ej: /api/tags) - Nombre (identificador visual) --- ## 5. Modelos de Datos ### Contenedor ```dart class Contenedor { String hash; // 64 chars, generado localmente String? titulo; String? descripcion; List archivos; GpsLocation? gps; List etiquetas; // Lista de hashes DateTime createdAt; } ``` ### Etiqueta ```dart class Etiqueta { String hMaestro; // Hash único (64 chars) String hGlobal; // biblioteca + etiqueta (128 chars) String? mrf; // Media reference para imagen String? ref; // Código corto String? nombreEs; String? nombreEn; String grupo; bool activo; } ``` ### Destino ```dart class Destino { int id; String nombre; String url; String hash; // Llave de autenticación bool activo; } ``` ### Pack ```dart class Pack { int id; String nombre; String icono; List tags; // Lista de hashes } ``` ### ArchivoAdjunto ```dart class ArchivoAdjunto { String nombre; String mimeType; Uint8List bytes; // En memoria, nunca en disco FileType tipo; // image, audio, video, document String? hash; } ``` --- ## 6. Base de Datos SQLite ```sql CREATE TABLE registro ( hash TEXT PRIMARY KEY, titulo TEXT, hora_envio TEXT NOT NULL, primera_conf TEXT, ultima_conf TEXT, destino_id INTEGER ); CREATE TABLE destinos ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL, url TEXT NOT NULL, hash TEXT NOT NULL, activo INTEGER DEFAULT 1 ); CREATE TABLE bibliotecas ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL, url TEXT NOT NULL, endpoint TEXT NOT NULL, h_biblioteca TEXT ); CREATE TABLE etiquetas_cache ( h_maestro TEXT PRIMARY KEY, h_global TEXT, mrf TEXT, ref TEXT, nombre_es TEXT, nombre_en TEXT, grupo TEXT, biblioteca_id INTEGER ); CREATE TABLE packs ( id INTEGER PRIMARY KEY AUTOINCREMENT, nombre TEXT NOT NULL, icono TEXT DEFAULT 📦, tags TEXT NOT NULL ); CREATE TABLE pendientes ( hash TEXT PRIMARY KEY, titulo TEXT, contenido BLOB NOT NULL, intentos INTEGER DEFAULT 0, ultimo_intento TEXT, proximo_intento TEXT, destino_id INTEGER ); CREATE TABLE app_state ( key TEXT PRIMARY KEY, value TEXT ); ``` --- ## 7. APIs Externas ### Backend (envío) ```http POST {destino.url}/ingest X-Auth-Key: {destino.hash} Content-Type: application/json { "hash": "fc2fae65158ca7c1880dd3ff9b8ed9081107b50f9cdce08df30234fe166777ce", "titulo": "ferr-23", "descripcion": "Compra en ferretería", "etiquetas": ["a1b2c3d4...fc2fae65...", "a1b2c3d4...b2c3d4e5..."], "gps": {"lat": 43.3623, "long": -8.4115}, "archivos": [{"nombre": "foto.jpg", "tipo": "image/jpeg", "contenido": "base64..."}] } Response 200: { "ok": true, "received_at": "2024-12-20T10:00:00Z" } Response 409: { "error": "hash_exists" } ``` ### Envío por chunks (archivos grandes) ```http POST {destino.url}/upload/init { "hash": "...", "total_chunks": 50, "file_name": "video.mp4" } → { "upload_id": "..." } POST {destino.url}/upload/chunk/{upload_id}/{chunk_number} Body: bytes (512KB - 1MB por chunk) → { "ok": true } POST {destino.url}/upload/complete/{upload_id} → { "ok": true, "file_hash": "..." } ``` ### Bibliotecas (índice de etiquetas) ```http GET {biblioteca.url}/api/tags Response: { "count": 973, "biblioteca": { "h_biblioteca": "b7149f9e2106c566032aeb29a26e4c6cdd5f5c16b4421025c58166ee345740d1", "nombre": "HST", "publica": true }, "results": [ { "h_maestro": "fc2fae65...77ce", "h_global": "b7149f9e...fc2fae65...77ce", "mrf": "502dc114...", "ref": "inv", "nombre_es": "factura", "nombre_en": "invoice", "grupo": "hst", "imagen_url": "https://tzrtech.org/502dc...png", "activo": true } ] } ``` Imágenes: GET {biblioteca.url}/{mrf}.png --- ## 8. Formato de Referencias Hash simple (etiqueta): 64 caracteres hex ``` fc2fae65158ca7c1880dd3ff9b8ed9081107b50f9cdce08df30234fe166777ce ``` Referencia global (biblioteca + etiqueta): 128 caracteres ``` [h_biblioteca 64 chars][h_etiqueta 64 chars] ``` Cadena jerárquica: múltiplo de 64, sin separador ``` [biblioteca][nivel1][nivel2][nivel3]... ``` Múltiples cadenas: separadas por \\n --- ## 9. Lógica de Reintentos 20 intentos en 72 horas con backoff exponencial: | Intento | Espera | Acumulado | |---------|--------|-----------| | 1 | 1 min | 0 | | 2 | 2 min | 1 min | | 3 | 5 min | 3 min | | 4 | 10 min | 8 min | | 5 | 20 min | 18 min | | 6 | 30 min | 38 min | | 7 | 1 h | 1 h | | 8 | 2 h | 2 h | | 9 | 3 h | 4 h | | 10 | 4 h | 7 h | | 11 | 5 h | 11 h | | 12 | 6 h | 16 h | | 13 | 6 h | 22 h | | 14 | 6 h | 28 h | | 15 | 8 h | 34 h | | 16 | 8 h | 42 h | | 17 | 8 h | 50 h | | 18 | 8 h | 58 h | | 19 | 6 h | 66 h | | 20 | STOP | 72 h | --- ## 10. Persistencia de Estado La app restaura al abrir exactamente como se cerró: - Destino seleccionado - Pantalla activa - Contenedor en progreso - Etiquetas seleccionadas - Cola de pendientes - Configuración completa --- ## 11. Permisos Android: ```xml ``` iOS: ```xml NSCameraUsageDescription Para capturar fotos y videos NSMicrophoneUsageDescription Para grabar notas de voz NSLocationWhenInUseUsageDescription Para añadir ubicación a los contenedores ``` --- ## 12. CI/CD ```yaml name: Build on: push: branches: [main, develop] pull_request: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter pub get - run: flutter test build-android: needs: test runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter build apk --release build-ios: needs: test runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: subosito/flutter-action@v2 - run: flutter build ios --release --no-codesign ``` --- ## 13. Roadmap ### v1.0 (MVP) - 5 pantallas básicas - Captura foto/audio/video - Envío a backend único - Cola de pendientes - Packs locales ### v1.1 - Múltiples destinos - Envío por chunks - Cache de bibliotecas ### v1.2 - Notificaciones push - Widgets de acceso rápido ### v2.0 - Credenciales para bibliotecas privadas - Cadenas jerárquicas visuales - Sincronización entre dispositivos --- ## 14. Referencias - Biblioteca HST: https://tzrtech.org - API HST: https://tzrtech.org/api/tags - h_biblioteca HST: b7149f9e2106c566032aeb29a26e4c6cdd5f5c16b4421025c58166ee345740d1 --- Versión: 1.0 Fecha: 2025-12-20