# FASE 1: PIPELINE MÍNIMO VIABLE **Complejidad:** Media **Duración estimada:** 2-3 días **Prioridad:** CRÍTICA --- ## OBJETIVO Establecer el flujo: PACKET → CLARA → PostgreSQL + R2 Esto permite que la app móvil envíe contenido y se almacene correctamente. --- ## PREREQUISITOS - [x] FASE 0 completada - [x] SSH acceso a DECK (72.62.1.113) - [x] R2 bucket 'deck' accesible - [x] PostgreSQL en DECK funcionando - [x] HST funcionando (tags) --- ## PASO 1.1: Crear tablas de CLARA en DECK ### Conectar a DECK ```bash ssh -i /home/orchestrator/.ssh/tzzr root@72.62.1.113 ``` ### Ejecutar SQL ```bash sudo -u postgres psql -d tzzr << 'EOF' -- Tabla principal de log CREATE TABLE IF NOT EXISTS clara_log ( id BIGSERIAL PRIMARY KEY, h_instancia VARCHAR(64) NOT NULL, h_entrada VARCHAR(64) NOT NULL, contenedor JSONB NOT NULL, r2_paths JSONB DEFAULT '{}', estado VARCHAR(20) DEFAULT 'recibido', procesado_at TIMESTAMP, created_at TIMESTAMP DEFAULT NOW(), CONSTRAINT clara_log_h_entrada_unique UNIQUE (h_entrada) ); -- Índices para búsqueda eficiente CREATE INDEX IF NOT EXISTS idx_clara_h_instancia ON clara_log(h_instancia); CREATE INDEX IF NOT EXISTS idx_clara_estado ON clara_log(estado); CREATE INDEX IF NOT EXISTS idx_clara_created ON clara_log(created_at DESC); CREATE INDEX IF NOT EXISTS idx_clara_contenedor ON clara_log USING gin(contenedor); -- Comentarios COMMENT ON TABLE clara_log IS 'Log inmutable de entrada - recibe contenedores de PACKET'; COMMENT ON COLUMN clara_log.h_instancia IS 'Hash SHA-256 que identifica la instancia DECK'; COMMENT ON COLUMN clara_log.h_entrada IS 'Hash SHA-256 único del contenedor'; COMMENT ON COLUMN clara_log.contenedor IS 'Contenedor completo en formato JSON (S-CONTRACT)'; COMMENT ON COLUMN clara_log.r2_paths IS 'Rutas de archivos subidos a R2'; COMMENT ON COLUMN clara_log.estado IS 'Estado: recibido, en_mason, en_feldman, consolidado'; -- Verificar SELECT 'Tabla clara_log creada correctamente' AS resultado; \dt clara_log \d clara_log EOF ``` ### Verificación ```bash sudo -u postgres psql -d tzzr -c "SELECT COUNT(*) FROM clara_log;" # Debe retornar 0 (tabla vacía) ``` ### Rollback ```sql DROP TABLE IF EXISTS clara_log CASCADE; ``` --- ## PASO 1.2: Generar h_instancia para DECK ### Concepto h_instancia es un hash SHA-256 único que identifica esta instancia de DECK. ### Generar ```bash # En DECK SEED="deck-personal-$(hostname)-$(date +%s)" H_INSTANCIA=$(echo -n "$SEED" | sha256sum | cut -d' ' -f1) echo "H_INSTANCIA=$H_INSTANCIA" # Guardar en archivo de configuración echo "H_INSTANCIA=$H_INSTANCIA" >> /opt/clara/.env ``` ### Verificación El hash debe tener 64 caracteres hexadecimales. --- ## PASO 1.3: Desplegar CLARA como Docker ### Crear estructura en DECK ```bash ssh -i /home/orchestrator/.ssh/tzzr root@72.62.1.113 << 'REMOTE' mkdir -p /opt/clara cd /opt/clara # Clonar repo git clone http://localhost:3000/tzzr/clara.git . # Crear .env cat > .env << 'EOF' # Generado automáticamente H_INSTANCIA= # PostgreSQL DB_HOST=172.17.0.1 DB_PORT=5432 DB_NAME=tzzr DB_USER=deck DB_PASSWORD= # Cloudflare R2 R2_ENDPOINT=https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com R2_ACCESS_KEY=ecddc771824c3cb3417d9451780db3d2 R2_SECRET_KEY=c8138e2597100ffb7dd1477ad722c0214f86097cd968752aea3cfcea5d54dbac R2_BUCKET=deck # Puerto PORT=5051 EOF # Construir y levantar docker-compose up -d --build # Verificar docker-compose logs --tail=20 REMOTE ``` ### docker-compose.yml actualizado ```yaml version: '3.8' services: clara: build: . container_name: clara ports: - "5051:5051" env_file: - .env restart: unless-stopped healthcheck: test: ["CMD", "curl", "-f", "http://localhost:5051/health"] interval: 30s timeout: 10s retries: 3 logging: driver: "json-file" options: max-size: "10m" max-file: "3" networks: default: external: name: deck_network ``` ### Dockerfile ```dockerfile FROM python:3.11-slim WORKDIR /app # Dependencias del sistema RUN apt-get update && apt-get install -y --no-install-recommends \ curl \ && rm -rf /var/lib/apt/lists/* # Dependencias Python COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Código COPY app.py . # Puerto EXPOSE 5051 # Ejecutar CMD ["python", "app.py"] ``` ### Verificación ```bash # Desde DECK curl http://localhost:5051/health # Debe retornar: {"service": "clara", "status": "ok", ...} # Desde ARCHITECT curl http://72.62.1.113:5051/health ``` --- ## PASO 1.4: Configurar Caddy para /ingest ### Editar Caddyfile en DECK ```bash ssh -i /home/orchestrator/.ssh/tzzr root@72.62.1.113 << 'REMOTE' # Añadir a Caddyfile existente cat >> /etc/caddy/Caddyfile << 'EOF' # CLARA - API de ingesta clara.tzzrdeck.me { reverse_proxy localhost:5051 } # También en el dominio principal tzzrdeck.me { handle /ingest* { reverse_proxy localhost:5051 } # ... resto de configuración } EOF # Recargar Caddy systemctl reload caddy REMOTE ``` ### Verificación ```bash # HTTPS (si DNS configurado) curl https://clara.tzzrdeck.me/health # O directamente curl http://72.62.1.113:5051/health ``` --- ## PASO 1.5: Probar ingesta completa ### Test desde curl ```bash # Definir variables H_INSTANCIA="" TEST_HASH=$(echo -n "test-$(date +%s)" | sha256sum | cut -d' ' -f1) # Enviar contenedor de prueba curl -X POST http://72.62.1.113:5051/ingest \ -H "Content-Type: application/json" \ -H "X-Auth-Key: $H_INSTANCIA" \ -d '{ "id": "'$TEST_HASH'", "archivo_hash": "'$TEST_HASH'", "origen": { "app": "curl-test", "version": "1.0", "timestamp_captura": "'$(date -Iseconds)'" }, "contenido": { "titulo": "Test de ingesta", "descripcion": "Contenedor de prueba desde curl" }, "etiquetas": [] }' ``` ### Respuestas esperadas ```json // Éxito {"ok": true, "id": 1, "h_entrada": "abc123..."} // Duplicado {"error": "hash_exists"} // Sin auth {"error": "unauthorized"} ``` ### Verificar en PostgreSQL ```bash ssh -i /home/orchestrator/.ssh/tzzr root@72.62.1.113 \ "sudo -u postgres psql -d tzzr -c 'SELECT id, h_entrada, estado, created_at FROM clara_log ORDER BY id DESC LIMIT 5;'" ``` --- ## PASO 1.6: Probar subida a R2 ### Test con archivo ```bash # Crear archivo de prueba echo "Contenido de prueba" > /tmp/test.txt TEST_DATA=$(base64 /tmp/test.txt) TEST_HASH=$(echo -n "test-file-$(date +%s)" | sha256sum | cut -d' ' -f1) curl -X POST http://72.62.1.113:5051/ingest \ -H "Content-Type: application/json" \ -H "X-Auth-Key: $H_INSTANCIA" \ -d '{ "id": "'$TEST_HASH'", "archivo_hash": "'$TEST_HASH'", "origen": { "app": "curl-test", "version": "1.0" }, "archivos": [{ "nombre": "test.txt", "tipo": "text/plain", "data": "'$TEST_DATA'" }] }' ``` ### Verificar en R2 ```bash # Usando AWS CLI configurado para R2 aws s3 ls s3://deck/ --endpoint-url https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com ``` --- ## CHECKLIST FINAL FASE 1 - [ ] 1.1 - Tabla clara_log creada en DECK PostgreSQL - [ ] 1.2 - h_instancia generado y guardado - [ ] 1.3 - CLARA corriendo en Docker - [ ] 1.4 - Caddy configurado (opcional) - [ ] 1.5 - Test de ingesta exitoso - [ ] 1.6 - Archivo subido a R2 - [ ] Health check responde correctamente - [ ] Logs sin errores --- ## MÉTRICAS DE ÉXITO | Métrica | Valor Esperado | |---------|----------------| | /health responde | < 100ms | | /ingest (sin archivo) | < 500ms | | /ingest (con archivo) | < 2s | | Registros en clara_log | > 0 | | Archivos en R2/deck | > 0 | --- ## TROUBLESHOOTING ### CLARA no arranca ```bash docker logs clara # Verificar variables de entorno docker exec clara env | grep -E "DB_|R2_" ``` ### No conecta a PostgreSQL ```bash # Verificar red docker exec clara ping -c1 172.17.0.1 # Verificar puerto docker exec clara nc -zv 172.17.0.1 5432 ``` ### Error R2 ```bash # Verificar credenciales docker exec clara python -c " import boto3 s3 = boto3.client('s3', endpoint_url='https://7dedae6030f5554d99d37e98a5232996.r2.cloudflarestorage.com', aws_access_key_id='ecddc771824c3cb3417d9451780db3d2', aws_secret_access_key='c8138e2597100ffb7dd1477ad722c0214f86097cd968752aea3cfcea5d54dbac' ) print(s3.list_buckets()) " ``` --- ## SIGUIENTE FASE Continuar con [FASE_2_PROCESAMIENTO_IA.md](FASE_2_PROCESAMIENTO_IA.md)