diff --git a/README.md b/README.md index 3c2783f..8cee93e 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,115 @@ -# packet +# Packet -A new Flutter project. +App móvil multiplataforma (iOS/Android) para capturar contenido multimedia, etiquetarlo con hashes y enviarlo a backends configurables. -## Getting Started +## Principios -This project is a starting point for a Flutter application. +| Principio | Descripción | +|-----------|-------------| +| Zero-retention | Los archivos nunca se almacenan en disco, solo en RAM hasta envío | +| Hash-first | Todo contenedor tiene hash SHA-256 ú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 | -A few resources to get you started if this is your first Flutter project: +## Stack Tecnológico -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +| 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 | -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +## Pantallas + +``` +┌─────────────────────────────────────────┐ +│ [Mi DECK ▼] [CORP] [+Ext] │ ← Destinos (superior) +├─────────────────────────────────────────┤ +│ CONTENIDO PANTALLA │ +├─────────────────────────────────────────┤ +│ 📷 🏷️ 📦 ⏳ ⚙️ │ ← Navegación (inferior) +│ Captura Tags Packs Pend. Config │ +└─────────────────────────────────────────┘ +``` + +### Captura +Montar contenedor con contenido multimedia: título, descripción, archivos, GPS, etiquetas. + +### Etiquetas +Seleccionar etiquetas de bibliotecas HST/DECK y añadir hashes externos. + +### Packs +Conjuntos predefinidos de etiquetas para aplicar con 1 tap. + +### Pendientes +Cola de contenedores fallidos (máx 20). App se bloquea si se llena. + +### Config +URLs, llaves de autenticación y bibliotecas de iconos. + +## Estructura del Proyecto + +``` +lib/ +├── core/ +│ ├── constants/ # Constantes de la app +│ ├── errors/ # Excepciones personalizadas +│ ├── utils/ # Utilidades (hash, retry) +│ └── theme/ # Tema de la app +├── data/ +│ ├── datasources/ # APIs y BD local +│ └── repositories/ # Repositorios +├── domain/ +│ └── entities/ # Modelos de dominio +└── presentation/ + ├── bloc/ # Cubits de estado + ├── pages/ # Pantallas + └── widgets/ # Widgets reutilizables +``` + +## Build + +```bash +# Instalar dependencias +flutter pub get + +# Build APK +flutter build apk --release + +# Build iOS (requiere Xcode) +flutter build ios --release +``` + +## APIs + +### Backend (envío) +```http +POST {destino.url}/ingest +X-Auth-Key: {destino.hash} +Content-Type: application/json +``` + +### Bibliotecas (etiquetas) +```http +GET {biblioteca.url}/api/tags +``` + +## Lógica de Reintentos + +20 intentos en 72 horas con backoff exponencial (1min → 8h). + +## Referencias + +- Biblioteca HST: https://tzrtech.org +- Especificación completa: [docs/SPEC.md](docs/SPEC.md) + +--- + +v1.0.0 diff --git a/docs/SPEC.md b/docs/SPEC.md new file mode 100644 index 0000000..6492b5b --- /dev/null +++ b/docs/SPEC.md @@ -0,0 +1,565 @@ +# 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