420 lines
8.4 KiB
Markdown
420 lines
8.4 KiB
Markdown
|
|
# 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=<PEGAR_HASH_GENERADO>
|
||
|
|
|
||
|
|
# PostgreSQL
|
||
|
|
DB_HOST=172.17.0.1
|
||
|
|
DB_PORT=5432
|
||
|
|
DB_NAME=tzzr
|
||
|
|
DB_USER=deck
|
||
|
|
DB_PASSWORD=<PASSWORD_DECK>
|
||
|
|
|
||
|
|
# 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="<hash_de_paso_1.2>"
|
||
|
|
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)
|