# πŸ—οΈ ARQUITECTURA CONTENT ADAPTER - Sistema de Contenido Externo **Fecha:** 2025-01-06 **Estado:** ImplementaciΓ³n Inicial **Principio:** βœ… ADITIVO - NO modifica cΓ³digo existente --- ## 🎯 OBJETIVO Crear una capa de contenido externo que permita: - βœ… Gestionar contenido sin tocar cΓ³digo - βœ… Usar Content Pack JSON desde servidor - βœ… Fallback total a datos locales - βœ… La app funciona igual si el sistema falla --- ## 🧱 ARQUITECTURA ``` β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ APP PWA (Frontend) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ ContentAdapterFactory β”‚ β”‚ β”œβ”€β”€ ExternalContentAdapter (NUEVO) β”‚ ← Content Pack JSON β”‚ └── LocalContentAdapter (EXISTENTE) β”‚ ← procedures.ts, drugs.ts β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β–Ό β–Ό β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Content Pack JSON β”‚ β”‚ Datos Locales β”‚ β”‚ (Servidor) β”‚ β”‚ (CΓ³digo actual) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ## πŸ“¦ COMPONENTES CREADOS ### 1. ContentAdapter (`src/services/content-adapter.ts`) **Interfaz:** ```typescript interface ContentAdapter { getProtocol(id: string): Procedure | null; getDrug(id: string): Drug | null; getAllProtocols(): Procedure[]; getAllDrugs(): Drug[]; isAvailable(): boolean; } ``` **Implementaciones:** - `LocalContentAdapter` - Usa `procedures.ts` y `drugs.ts` (fallback) - `ExternalContentAdapter` - Usa Content Pack JSON (si disponible) **Factory:** - `ContentAdapterFactory.getAdapter()` - Decide automΓ‘ticamente - Prioridad: External β†’ Local --- ### 2. Generador Content Pack (`backend/src/services/pack-generator.js`) **Funcionalidad:** - Lee contenido publicado desde PostgreSQL - Incluye recursos multimedia asociados - Calcula hash SHA-256 - Genera JSON optimizado **MΓ©todo principal:** ```javascript await packGenerator.generatePack(version, options) ``` --- ### 3. API Content Pack (`backend/src/routes/content-pack.js`) **Endpoints:** - `GET /api/content-pack/latest.json` - Pack mΓ‘s reciente - `GET /api/content-pack/:version.json` - Pack especΓ­fico **CaracterΓ­sticas:** - βœ… Sin autenticaciΓ³n (pΓΊblico) - βœ… Cache headers (ETag, Cache-Control) - βœ… GeneraciΓ³n on-the-fly si no existe archivo - βœ… 304 Not Modified si hash coincide --- ## πŸ”„ FLUJO DE FUNCIONAMIENTO ### 1. App Inicia ``` App β†’ ContentAdapterFactory.getAdapter() β†’ ExternalContentAdapter (intenta cargar pack) β†’ Si existe: usa pack β†’ Si no existe: fallback a LocalContentAdapter ``` ### 2. Carga del Pack ``` ExternalContentAdapter β†’ Intenta cache (localStorage) β†’ Si no hay cache: descarga /api/content-pack/latest.json β†’ Guarda en cache (24h) β†’ Convierte ContentItem β†’ Procedure/Drug ``` ### 3. Fallback AutomΓ‘tico ``` Si ExternalContentAdapter no estΓ‘ disponible: β†’ LocalContentAdapter (siempre disponible) β†’ Usa procedures.ts y drugs.ts β†’ App funciona exactamente igual ``` --- ## πŸ“‹ FORMATO CONTENT PACK JSON ```json { "metadata": { "version": "1.0.0", "generated_at": "2025-01-06T12:00:00Z", "hash": "sha256:abc123...", "total_items": 24, "total_resources": 0 }, "content": { "protocols": [ { "id": "...", "type": "protocol", "slug": "rcp-adulto-svb", "title": "RCP Adulto - Soporte Vital BΓ‘sico", "content": { ... }, "priority": "critical", "status": "published", "version": "1.0.0" } ], "guides": [...], "drugs": [...], "checklists": [...] }, "media": { "resources": [...], "associations": [...] } } ``` --- ## βœ… GARANTÍAS DE SEGURIDAD ### 1. Fallback Total - βœ… Si el pack no existe β†’ usa datos locales - βœ… Si el pack falla al cargar β†’ usa datos locales - βœ… Si el pack estΓ‘ corrupto β†’ usa datos locales - βœ… La app NUNCA crashea por falta de pack ### 2. No Modifica CΓ³digo Existente - βœ… `procedures.ts` - NO modificado - βœ… `drugs.ts` - NO modificado - βœ… Componentes existentes - NO modificados - βœ… Service Worker - NO modificado - βœ… Rutas - NO modificadas ### 3. Compatibilidad Offline - βœ… Pack se cachea en localStorage - βœ… Funciona offline despuΓ©s de primera carga - βœ… Cache vΓ‘lido por 24 horas - βœ… Refresh manual disponible --- ## πŸš€ USO EN LA APP ### OpciΓ³n 1: Usar directamente ```typescript import { getProtocol, getAllProtocols } from '@/services/content-adapter'; // Obtener protocolo (automΓ‘ticamente usa pack o local) const protocol = getProtocol('rcp-adulto-svb'); // Obtener todos (automΓ‘ticamente usa pack o local) const protocols = getAllProtocols(); ``` ### OpciΓ³n 2: Usar hook React ```typescript import { useContentAdapter } from '@/services/content-adapter'; function MyComponent() { const { getProtocol, getAllProtocols } = useContentAdapter(); const protocol = getProtocol('rcp-adulto-svb'); // ... } ``` ### OpciΓ³n 3: Forzar refresh ```typescript import { refreshContentPack } from '@/services/content-adapter'; // Forzar recarga del pack await refreshContentPack(); ``` --- ## πŸ”§ CONFIGURACIΓ“N ### Backend **Variables de entorno:** ```env PACKS_DIR=/ruta/a/storage/packs # Opcional ``` **Estructura de directorios:** ``` backend/ β”œβ”€β”€ storage/ β”‚ └── packs/ β”‚ β”œβ”€β”€ pack-v1.0.0.json β”‚ └── pack-latest.json (symlink) ``` ### Frontend **No requiere configuraciΓ³n** - Funciona automΓ‘ticamente **Cache:** - LocalStorage: `content_pack` y `content_pack_time` - TTL: 24 horas - Refresh manual disponible --- ## πŸ“Š ESTADO ACTUAL ### βœ… Implementado - [x] ContentAdapter interface - [x] LocalContentAdapter (fallback) - [x] ExternalContentAdapter (pack) - [x] ContentAdapterFactory - [x] Generador de Content Pack - [x] API endpoints para pack - [x] Cache en localStorage - [x] ConversiΓ³n ContentItem β†’ Procedure/Drug ### ⏳ Pendiente - [ ] Integrar en componentes existentes (opcional) - [ ] Service Worker para cache avanzado (opcional) - [ ] IndexedDB para packs grandes (opcional) - [ ] UI para forzar refresh (opcional) --- ## πŸ§ͺ TESTING ### Verificar que funciona ```typescript // 1. Verificar adapter disponible import { contentAdapter } from '@/services/content-adapter'; console.log('Adapter disponible:', contentAdapter.isAvailable()); // 2. Obtener protocolo const protocol = contentAdapter.getProtocol('rcp-adulto-svb'); console.log('Protocolo:', protocol); // 3. Verificar fallback // Desconectar servidor β†’ deberΓ­a usar local ``` ### Verificar Content Pack ```bash # Obtener pack curl http://localhost:3000/api/content-pack/latest.json # Verificar hash # El hash debe coincidir con ETag header ``` --- ## ⚠️ RESTRICCIONES CUMPLIDAS - βœ… NO modifica `src/data/procedures.ts` - βœ… NO modifica `src/data/drugs.ts` - βœ… NO modifica Service Worker - βœ… NO modifica rutas existentes - βœ… NO modifica componentes actuales - βœ… TODO es aditivo y desacoplado - βœ… Fallback total garantizado - βœ… App funciona igual si falla --- **Fin del Documento**