Cliente: Centrica Soluciones
Proyecto: Nebula ERP - Componente DataVault
Version: 1.0
Fecha: 16 de marzo, 2026
Autor: Carlos Torres (Arquitecto de Software)
Estado: VIGENTE
DataVault se conecta con tres sistemas externos para transferencia y almacenamiento de paquetes archivisticos:
+-------------------+
| DataVault |
| (Servidor) |
+--------+----------+
|
+----------------+----------------+
| | |
v v v
+-----------+ +-----------+ +----------------+
| Azure | | SFTP | | Archivematica |
| Blob | | Staging | | (Opcional) |
| Storage | | Server | | |
+-----------+ +-----------+ +----------------+
Backup cloud Upload SIPs Sistema OAIS
AIP/DIP/SIP a VM staging externo
Cliente: app/clients/azure_client.py
SDK: azure-storage-blob (Python)
class AzureClient:
def __init__(self, connection_string, container_name):
"""
Soporta dos modos de conexion:
1. Azure standard: https://{account_name}.blob.core.windows.net
2. Azurite (local): http://127.0.0.1:10000/devstoreaccount1
"""
if custom_endpoint:
self.blob_service = BlobServiceClient(
account_url=custom_endpoint,
credential=account_key
)
else:
self.blob_service = BlobServiceClient.from_connection_string(
connection_string
)
def upload_blob(self, blob_name, local_file_path) -> bool:
"""Upload de archivo individual a Azure Blob Storage"""
blob_client = self.container_client.get_blob_client(blob_name)
with open(local_file_path, 'rb') as data:
blob_client.upload_blob(data, overwrite=True)
return True
# .env (produccion)
AZURE_STORAGE_CONNECTION_STRING=DefaultEndpointsProtocol=https;AccountName=...
AZURE_CONTAINER_NAME=datavault-backup
# .env (desarrollo con Azurite)
AZURE_CUSTOM_ENDPOINT=http://127.0.0.1:10000/devstoreaccount1
AZURE_ACCOUNT_KEY=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==
Tres daemons independientes sincronizan paquetes a Azure:
| Daemon | Frecuencia | Tipo | Directorio Fuente |
|---|---|---|---|
cloud_sync_daemon_sip.py |
1 min | SIP (carpetas) | {PROJECT}/SIP/ |
cloud_sync_daemon_aip.py |
5 min | AIP (ZIPs) | {PROJECT}/AIP/ |
cloud_sync_daemon.py |
5 min | DIP (ZIPs) | {PROJECT}/DIP/ |
Estructura en Azure Blob:
datavault-backup/
SIP/
DIGITALIZACION/
{unique_id}/data/...
{unique_id}/metadata/...
AIP/
DIGITALIZACION/
{unique_id}.zip
DIP/
DIGITALIZACION/
{unique_id}.zip
Cada daemon mantiene un registro en SQLite local (logs/cloud_sync.db):
-- Tabla de tracking por daemon
CREATE TABLE sync_status (
file_path TEXT PRIMARY KEY,
sha256_hash TEXT,
synced_at TIMESTAMP,
error_count INTEGER DEFAULT 0,
last_error TEXT
);
Logica de sincronizacion:
sync_statussync_statuserror_count, registrar error| Aspecto | Estado | Observacion |
|---|---|---|
| Retry logic | Parcial | Solo via SDK de Azure (retry automatico). Sin retry a nivel de aplicacion. |
| Concurrencia | Riesgo | Tres daemons pueden escribir a cloud_sync.db simultaneamente. SQLite no maneja bien la concurrencia de escritura. |
| Bandwidth control | Ausente | Sin throttling. Un upload de 50GB puede saturar la conexion. |
| Cifrado en transito | OK | HTTPS por defecto en Azure SDK. |
| Cifrado en reposo | Depende | Azure lo soporta si se configura en el storage account. No se fuerza desde el cliente. |
| Idempotencia | OK | overwrite=True garantiza que re-uploads son seguros. |
| Monitoring | Basico | Solo logs a archivo. Sin metricas de bytes transferidos o tiempo de upload. |
Cliente: app/clients/sftp_client.py
SDK: paramiko + tqdm
class SFTPClient:
def __init__(self):
self.host = "192.168.68.114"
self.port = 22
self.username = "egs"
self.password = "12345" # CRITICO: hardcoded en .env
self.remote_base = "/home/egs/datavault/Repositorio/ingest_sip"
def test_connection(self) -> bool:
"""Verifica conexion SFTP al servidor staging"""
def upload(self, local_path, remote_subdir) -> bool:
"""Upload con barra de progreso via tqdm"""
Usuario Web (Frontend)
|
v
FastAPI Backend
|
+-- SFTPClient.upload(local_path, remote_subdir)
| |
| v
| paramiko.Transport("192.168.68.114:22")
| |
| v
| sftp.put(local, remote, callback=tqdm)
| |
| v
+-- Servidor Staging (192.168.68.114)
|
v
/home/egs/datavault/Repositorio/ingest_sip/
|
v
Watcher detecta y procesa
| Aspecto | Estado | Severidad |
|---|---|---|
| Credenciales en texto plano | SFTP_PASSWORD=12345 en .env |
CRITICA |
| Password debil | "12345" como password de produccion | CRITICA |
| Sin SSH keys | Usa password auth en lugar de key-based auth | ALTA |
| Sin cifrado del canal | SFTP usa SSH, pero sin verificacion de host key | ALTA |
| Sin compresion | Transferencia sin compresion antes de envio | MEDIA |
| Sin resume | Si el upload se interrumpe, debe reiniciar completo | MEDIA |
| IP hardcoded | 192.168.68.114 en codigo, no configurable |
BAJA |
Migrar a autenticacion por SSH keys:
# En servidor staging
ssh-keygen -t ed25519 -C "datavault-service"
ssh-copy-id -i ~/.ssh/datavault_ed25519.pub egs@192.168.68.114
Eliminar password del .env: Usar SFTP_KEY_PATH=/path/to/private_key en su lugar.
Verificar host key:
# Agregar al SFTPClient.__init__
known_hosts = paramiko.HostKeys()
known_hosts.load(os.path.expanduser("~/.ssh/known_hosts"))
Implementar resume de upload via sftp.stat() para detectar tamanio parcial y continuar.
Cliente: app/clients/archivematica_client.py
La integracion con Archivematica existe como cliente pero no esta activa en produccion. Archivematica es un sistema OAIS externo de codigo abierto que podria complementar las capacidades nativas de DataVault.
Si Centrica requiere certificacion formal de preservacion digital o integracion con estandares internacionales mas estrictos, Archivematica podria servir como capa de validacion externa:
DataVault (interno) --> Archivematica API --> Validacion OAIS formal
| Integracion | Protocolo | Autenticacion | Estado | Criticidad |
|---|---|---|---|---|
| Azure Blob Storage | HTTPS | Connection String / Key | Activa | ALTA |
| SFTP Staging | SSH/SFTP | Password (inseguro) | Activa | ALTA |
| Archivematica | REST API | API Key | Inactiva | BAJA |
| SMTP (Email) | TLS/SMTP | App Password | Activa | MEDIA |
Departamento de Arquitectura — Centrica Soluciones