7.5 KiB
7.5 KiB
🏗️ 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:
interface ContentAdapter {
getProtocol(id: string): Procedure | null;
getDrug(id: string): Drug | null;
getAllProtocols(): Procedure[];
getAllDrugs(): Drug[];
isAvailable(): boolean;
}
Implementaciones:
LocalContentAdapter- Usaprocedures.tsydrugs.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:
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 recienteGET /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
{
"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
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
import { useContentAdapter } from '@/services/content-adapter';
function MyComponent() {
const { getProtocol, getAllProtocols } = useContentAdapter();
const protocol = getProtocol('rcp-adulto-svb');
// ...
}
Opción 3: Forzar refresh
import { refreshContentPack } from '@/services/content-adapter';
// Forzar recarga del pack
await refreshContentPack();
🔧 CONFIGURACIÓN
Backend
Variables de entorno:
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_packycontent_pack_time - TTL: 24 horas
- Refresh manual disponible
📊 ESTADO ACTUAL
✅ Implementado
- ContentAdapter interface
- LocalContentAdapter (fallback)
- ExternalContentAdapter (pack)
- ContentAdapterFactory
- Generador de Content Pack
- API endpoints para pack
- Cache en localStorage
- 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
// 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
# 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