- Introducción
- Arquitectura del Sistema
- Requisitos Previos
- Instalación y Configuración
- Modelo de Datos
- Endpoints de la API
- Flujos de Operación Detallados
- Estructura de Base de Datos
- Chaincode (Smart Contract)
- Monitoreo y Métricas
- Interfaz Web
- Troubleshooting
- Mejores Prácticas
Este proyecto es una API REST empresarial desarrollada en Node.js con Express, diseñada para interactuar con una red de Hyperledger Fabric (HLF). Su propósito principal es almacenar y recuperar datos JSON en la blockchain de HLF de manera eficiente, utilizando contratos inteligentes (chaincodes) para operaciones de escritura y lectura garantizando inmutabilidad y trazabilidad.
Doble Modelo de Almacenamiento: Sistema híbrido que combina blockchain y base de datos relacional
Trazabilidad Completa: Cada transacción es registrada con timestamps en nanosegundos
Monitoreo Avanzado: Integración con Prometheus para métricas de rendimiento en tiempo real
Arquitectura Escalable: Separación de cargas de trabajo mediante canales dedicados
Interfaz Web Integrada: Dashboard para visualización y gestión de registros
Seguridad Blockchain: Autenticación mediante identidades de wallet de Fabric
El proyecto implementa dos estrategias de almacenamiento optimizadas para diferentes casos de uso:
- En Blockchain: Solo almacena un hash SHA-256 del JSON (32 bytes)
- En MySQL: Almacena el JSON completo para consultas rápidas
- Uso recomendado: Datos que requieren consultas frecuentes pero inmutabilidad limitada
- Ventaja: Reduce la carga en el ledger de blockchain, mejora el rendimiento
- En Blockchain: Almacena el JSON completo de forma inmutable
- En MySQL: Solo almacena metadatos (sin el JSON)
- Uso recomendado: Datos críticos que requieren inmutabilidad total y auditabilidad
- Ventaja: Máxima trazabilidad y verificabilidad criptográfica
Ambos modelos utilizan canales separados en HLF (lightchannel y heavychannel) para optimizar el rendimiento y permitir una gestión independiente de políticas de endorsement.
La aplicación sigue una arquitectura de 3 capas con integración blockchain:
1. Capa de Presentación: Cliente (Web/API)
2. Capa de Lógica de Negocio: API REST (Node.js + Express)
3. Capa de Datos: Hyperledger Fabric + MySQL + Prometheus
- Cliente → API: Solicitud HTTP (POST/GET)
- API → Fabric: Invocación de chaincode usando SDK de Fabric
- API → MySQL: Persistencia de metadatos y datos (según modelo)
- API → Prometheus: Consulta de métricas de rendimiento
- API → Cliente: Respuesta JSON con resultado y metadatos
| Componente | Tecnología | Función |
|---|---|---|
| Servidor API | Node.js 14+ + Express 5.x | Manejo de peticiones HTTP y lógica de negocio |
| Blockchain | Hyperledger Fabric 2.2 | Almacenamiento inmutable de datos |
| Base de Datos | MySQL 8.0+ | Almacenamiento de metadatos y consultas rápidas |
| Monitoreo | Prometheus | Métricas de rendimiento y observabilidad |
| Autenticación | Fabric Wallet | Gestión de identidades y certificados |
| Frontend | HTML5/CSS3/JS Vanilla | Dashboard de visualización |
-
Node.js (v14 o superior)
node --version # Verificar versión -
MySQL Server (v8.0 o superior)
mysql --version
-
Hyperledger Fabric Test Network en ejecución
- Fabric Samples clonado
- Test network iniciado con canales
lightchannelyheavychannel - Chaincode
jsonstoragemodeldeployado en ambos canales
-
Prometheus (opcional, para métricas)
- Instalado y configurado para scraping de peers de Fabric
- Fundamentos de blockchain y Hyperledger Fabric
- Node.js y desarrollo de APIs REST
- SQL y modelado de bases de datos
- Conceptos de observabilidad (opcional)
git clone https://github.com/GSYAtools/blockchain-api.git
cd blockchain-apinpm installCrea un archivo .env basado en example.env:
cp example.env .envEdita el archivo .env con tus configuraciones:
# --- API ---
PORT=3460
# --- MySQL ---
DB_HOST=127.0.0.1
DB_PORT=3306
DB_USER=blockchain_user
DB_PASSWORD=tu_password_seguro
DB_NAME=blockchain_hlf
# --- Fabric Network ---
CCP_PATH=./connection-org1.json
WALLET_PATH=./wallet
IDENTITY=Admin@org1.example.com
# Canales
LIGHT_CHANNEL=lightchannel
HEAVY_CHANNEL=heavychannel
# Chaincodes
CHAINCODE_NAME=jsonstoragemodel
# Prometheus
PROMETHEUS_URL=http://localhost:9090CREATE DATABASE blockchain_hlf CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE blockchain_hlf;
-- Tabla para modelo LIGHT
CREATE TABLE light_model_data (
id INT AUTO_INCREMENT PRIMARY KEY,
data JSON NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
start_tx_ns BIGINT UNSIGNED,
end_tx_ns BIGINT UNSIGNED,
tx_id VARCHAR(255) UNIQUE NOT NULL,
INDEX idx_tx_id (tx_id),
INDEX idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabla para modelo HEAVY
CREATE TABLE heavy_model_data (
id INT AUTO_INCREMENT PRIMARY KEY,
tx_id VARCHAR(255) UNIQUE NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
start_tx_ns BIGINT UNSIGNED,
end_tx_ns BIGINT UNSIGNED,
INDEX idx_tx_id (tx_id),
INDEX idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;npm run importEste comando ejecuta:
importConnection.js: Copia el perfil de conexión desde Fabric test-networkimportIdentity.js: Importa la identidad de Admin a la wallet local
npm startLa API estará disponible en http://localhost:3460
# Probar endpoint de registros
curl http://localhost:3460/registros
# Acceder a la interfaz web
# Abrir navegador en http://localhost:3460| Característica | Light Model | Heavy Model |
|---|---|---|
| Dato en Blockchain | Hash SHA-256 (32 bytes) | JSON completo |
| Dato en MySQL | JSON completo | Solo metadatos |
| Tamaño en Ledger | Fijo, mínimo | Variable, según JSON |
| Velocidad de escritura | Muy rápida | Más lenta |
| Inmutabilidad del contenido | Parcial (hash) | Total (JSON) |
| Verificabilidad | Hash verificable | Contenido completo verificable |
| Costo de almacenamiento | Bajo en blockchain | Alto en blockchain |
| Casos de uso | Logs, métricas, datos frecuentes | Contratos, documentos legales, auditoría |
Ambos modelos aceptan cualquier estructura JSON válida. Ejemplo:
{
"usuario": "john_doe",
"accion": "compra",
"producto": {
"id": "PROD-001",
"nombre": "Laptop",
"precio": 1200.50
},
"timestamp": "2025-12-17T10:30:00Z",
"metadata": {
"ip": "192.168.1.100",
"dispositivo": "mobile"
}
}Endpoint: POST /guardar-json
Descripción: Guarda un objeto JSON en ambos modelos (light y heavy) simultáneamente.
Request Headers:
Content-Type: application/json
Request Body:
{
"data": {
"campo1": "valor1",
"campo2": "valor2",
"anidado": {
"subcampo": "valor"
}
},
"descripcion": "Descripción opcional del registro"
}Parámetros:
data(objeto, requerido): Datos JSON a almacenardescripcion(string, requerido): Descripción del registro
Response Success (200):
{
"message": "Guardado en light & heavy con nanosegundos en BD",
"txidLight": "a1b2c3d4e5f6...",
"txidHeavy": "f6e5d4c3b2a1..."
}Response Error (400):
{
"error": "Faltan campos requeridos (data, descripcion)"
}Response Error (500):
{
"error": "Mensaje de error detallado"
}Ejemplo con cURL:
curl -X POST http://localhost:3460/guardar-json \
-H "Content-Type: application/json" \
-d '{
"data": {
"nombre": "Juan",
"edad": 30,
"ciudad": "Madrid"
},
"descripcion": "Registro de usuario"
}'Ejemplo con JavaScript (fetch):
const response = await fetch('http://localhost:3460/guardar-json', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
data: { nombre: "Juan", edad: 30 },
descripcion: "Registro de usuario"
})
});
const result = await response.json();
console.log('TX IDs:', result.txidLight, result.txidHeavy);Endpoint: GET /leer-json/:tipo/:txid
Descripción: Recupera un registro específico usando su transaction ID.
Parámetros de URL:
tipo(string):lightoheavytxid(string): Transaction ID retornado al guardar
Response Success (200):
{
"payload": "hash_sha256..." , // Para light, o JSON completo para heavy
"tipo": "light",
"channel": "lightchannel",
"localData": "{\"nombre\":\"Juan\",\"edad\":30}", // Solo en light
"creator": {
"mspid": "Org1MSP",
"id_bytes": "-----BEGIN CERTIFICATE-----\n..."
},
"signature": "base64_signature...",
"tx_header": {
"channel_id": "lightchannel",
"tx_id": "a1b2c3d4e5f6...",
"timestamp": {
"seconds": 1702819800,
"nanos": 123456789
},
"type": 3
}
}Response Error (400):
{
"error": "Faltan parámetros requeridos (tipo, txid)"
}Response Error (404):
{
"error": "No se pudo obtener contenido de la base de datos"
}Ejemplo con cURL:
curl http://localhost:3460/leer-json/light/a1b2c3d4e5f6...Ejemplo con JavaScript:
const txid = 'a1b2c3d4e5f6...';
const tipo = 'light';
const response = await fetch(`http://localhost:3460/leer-json/${tipo}/${txid}`);
const data = await response.json();
console.log('Datos:', JSON.parse(data.localData));
console.log('Creador:', data.creator.mspid);Endpoint: GET /registros
Descripción: Obtiene todos los registros almacenados en MySQL (light y heavy).
Response Success (200):
{
"light": [
{
"id": 1,
"data": "{\"nombre\":\"Juan\",\"edad\":30}",
"timestamp": "2025-12-17T10:30:00.000Z",
"start_tx_ns": "1702819800000000000",
"end_tx_ns": "1702819800123456789",
"tx_id": "a1b2c3d4e5f6...",
"tipo": "light"
}
],
"heavy": [
{
"id": 1,
"timestamp": "2025-12-17T10:30:00.000Z",
"start_tx_ns": "1702819800000000000",
"end_tx_ns": "1702819800234567890",
"tx_id": "f6e5d4c3b2a1...",
"tipo": "heavy"
}
]
}Ejemplo con cURL:
curl http://localhost:3460/registrosEndpoint: GET /metrics/tx/:txid
Descripción: Obtiene métricas de rendimiento de Prometheus para una transacción específica.
Parámetros de URL:
txid(string): Transaction ID
Response Success (200):
{
"txid": "a1b2c3d4e5f6...",
"timestamp": "2025-12-17T10:30:00.000Z",
"duration_ms": 123.456,
"metrics": {
"blockLatency95": 450.5,
"blocksPerSec": 2.3,
"blockchainHeight": 1024,
"systemCpuPct": 45.2,
"hostMemUsagePct": 62.8,
"peerCpuPct": 12.3,
"peerMemBytes": 536870912
}
}Response Error (404):
{
"error": "tx_id no encontrado"
}Ejemplo con cURL:
curl http://localhost:3460/metrics/tx/a1b2c3d4e5f6...Endpoint: GET /
Descripción: Interfaz web HTML para visualizar y gestionar registros.
Accede desde tu navegador: http://localhost:3460
Endpoint: /data/*
Descripción: Ruta adicional para operaciones de datos (ver implementación en routes/data.js).
Diagramas interactivos disponibles:
- Flujo de Guardar Datos - POST /guardar-json
- Flujo de Leer Datos - GET /leer-json/:tipo/:txid
- Diagramas de Secuencia - Interacciones completas
Resumen del proceso:
- Validación: Verifica campos requeridos (data, descripcion)
- Conexión: Establece conexión con MySQL y Fabric
- Preparación: Genera hash SHA-256 del JSON
- Transacción Light: Submit a lightchannel con hash
- Transacción Heavy: Submit a heavychannel con JSON completo
- Persistencia: Guarda en ambas tablas de MySQL
- Respuesta: Retorna txidLight y txidHeavy
Tiempo estimado: 200-500ms
Ver diagrama de flujo completo: flujo-guardar-datos.md
Resumen del proceso:
- Validación: Verifica tipo (light/heavy) y txid
- Conexión: Conecta a Fabric con la identidad
- Selección: Elige canal según tipo (lightchannel/heavychannel)
- Query: Ejecuta GetDataByTxID en chaincode
- MySQL (solo light): Recupera JSON completo de base de datos
- Bloque: Obtiene metadatos del bloque (creator, signature)
- Respuesta: Retorna payload + metadatos
Tiempo estimado: 100-300ms
Ver diagrama de flujo completo: flujo-leer-datos.md
Puntos clave:
- Es una operación de lectura (evaluate), no modifica el ledger
- Para model light, se requiere consulta adicional a MySQL
- Retorna metadatos criptográficos del bloque (creator, signature)
- QSCC permite acceso de bajo nivel al bloque completo
CREATE TABLE light_model_data (
id INT AUTO_INCREMENT PRIMARY KEY,
data JSON NOT NULL, -- JSON completo
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
start_tx_ns BIGINT UNSIGNED, -- Inicio TX en nanosegundos
end_tx_ns BIGINT UNSIGNED, -- Fin TX en nanosegundos
tx_id VARCHAR(255) UNIQUE NOT NULL, -- Transaction ID de Fabric
INDEX idx_tx_id (tx_id),
INDEX idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Campos:
id: Identificador autoincrementaldata: Objeto JSON completo (tipo MySQL JSON nativo)timestamp: Fecha y hora de la transacciónstart_tx_ns: Timestamp de inicio en nanosegundos (BigInt)end_tx_ns: Timestamp de fin en nanosegundos (BigInt)tx_id: Transaction ID único de Hyperledger Fabric
Ejemplo de registro:
{
"id": 1,
"data": "{\"usuario\":\"john\",\"accion\":\"login\"}",
"timestamp": "2025-12-17 10:30:00",
"start_tx_ns": "1702819800000000000",
"end_tx_ns": "1702819800123456789",
"tx_id": "a1b2c3d4e5f6g7h8i9j0..."
}CREATE TABLE heavy_model_data (
id INT AUTO_INCREMENT PRIMARY KEY,
tx_id VARCHAR(255) UNIQUE NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP,
start_tx_ns BIGINT UNSIGNED,
end_tx_ns BIGINT UNSIGNED,
INDEX idx_tx_id (tx_id),
INDEX idx_timestamp (timestamp)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Diferencias con light_model_data:
- NO tiene columna
data(el JSON está en blockchain) - Solo almacena metadatos y referencias
Ejemplo de registro:
{
"id": 1,
"tx_id": "f6e5d4c3b2a1...",
"timestamp": "2025-12-17 10:30:00",
"start_tx_ns": "1702819800000000000",
"end_tx_ns": "1702819800234567890"
}Obtener duración de transacciones en microsegundos:
SELECT
tx_id,
timestamp,
(end_tx_ns - start_tx_ns) / 1000 as duration_us
FROM light_model_data
ORDER BY duration_us DESC
LIMIT 10;Buscar registros por contenido JSON (light):
SELECT * FROM light_model_data
WHERE JSON_EXTRACT(data, '$.usuario') = 'john_doe';Análisis de rendimiento por hora:
SELECT
DATE_FORMAT(timestamp, '%Y-%m-%d %H:00') as hour,
COUNT(*) as total_transactions,
AVG((end_tx_ns - start_tx_ns) / 1000000) as avg_duration_ms
FROM light_model_data
GROUP BY hour
ORDER BY hour DESC;jsonstoragemodel - Deployado en ambos canales
Descripción: Almacena datos en el ledger.
Parámetros:
tipo(string): "light" o "heavy"payload(string): Hash SHA-256 (light) o JSON completo (heavy)
Operación:
// Pseudo-código
func StoreData(ctx, tipo string, payload string) error {
key := ctx.GetTxID()
value := {
"tipo": tipo,
"payload": payload,
"timestamp": time.Now()
}
return ctx.PutState(key, value)
}Resultado: Transaction ID único
Descripción: Recupera datos del ledger por Transaction ID.
Parámetros:
tipo(string): "light" o "heavy"txid(string): Transaction ID
Operación:
func GetDataByTxID(ctx, tipo string, txid string) (string, error) {
value, err := ctx.GetState(txid)
if err != nil {
return "", err
}
return value, nil
}Resultado: Objeto con tipo, payload y timestamp
Recomendadas:
- Light Channel:
OR('Org1MSP.member', 'Org2MSP.member')- Más permisivo - Heavy Channel:
AND('Org1MSP.member', 'Org2MSP.member')- Más restrictivo
# Navegar a test-network
cd fabric-samples/test-network
# Crear canales
./network.sh createChannel -c lightchannel
./network.sh createChannel -c heavychannel
# Deploy chaincode en ambos canales
./network.sh deployCC -c lightchannel -ccn jsonstoragemodel -ccp ../chaincode/jsonstorage -ccl go
./network.sh deployCC -c heavychannel -ccn jsonstoragemodel -ccp ../chaincode/jsonstorage -ccl goLa API consulta Prometheus para obtener métricas de la red Fabric.
Métricas Disponibles:
| Métrica | Descripción | Query PromQL |
|---|---|---|
blockLatency95 |
Latencia de commit de bloques (p95) en μs | histogram_quantile(0.95, peer_block_commit_duration_seconds_bucket) |
blocksPerSec |
Bloques commiteados por segundo | rate(peer_committed_block_total[1m]) |
blockchainHeight |
Altura actual del blockchain | ledger_blockchain_height |
systemCpuPct |
% de CPU del host | 100 - avg(rate(node_cpu_seconds_total{mode="idle"}[1m])) * 100 |
hostMemUsagePct |
% de memoria del host | ((MemTotal - MemAvailable) / MemTotal) * 100 |
peerCpuPct |
% de CPU del peer | 100 * rate(process_cpu_seconds_total{job="peer"}[1m]) |
peerMemBytes |
Memoria del peer en bytes | process_resident_memory_bytes{job="peer"} |
Archivo prometheus.yml:
global:
scrape_interval: 15s
scrape_configs:
- job_name: 'peer0_org1'
static_configs:
- targets: ['peer0.org1.example.com:9443']
- job_name: 'orderer'
static_configs:
- targets: ['orderer.example.com:8443']
- job_name: 'node'
static_configs:
- targets: ['localhost:9100']Accede a GET /metrics/tx/:txid para visualizar:
{
"txid": "a1b2c3...",
"timestamp": "2025-12-17T10:30:00Z",
"duration_ms": 123.45,
"metrics": {
"blockLatency95": 450.5,
"blocksPerSec": 2.3,
"blockchainHeight": 1024,
"systemCpuPct": 45.2,
"hostMemUsagePct": 62.8,
"peerCpuPct": 12.3,
"peerMemBytes": 536870912
}
}Archivo: public/index.html
Funcionalidad:
- Lista todos los registros (light y heavy)
- Muestra timestamp, tx_id y tipo
- Permite navegar a detalles de cada registro
- Auto-refresh cada 30 segundos
Captura conceptual:
┌─────────────────────────────────────────────┐
│ Blockchain API Dashboard │
├─────────────────────────────────────────────┤
│ │
│ Filtrar: [tipo] [fecha] [Refresh] │
│ │
│ Registros LIGHT: │
│ ┌──────────────────────────────────────┐ │
│ │ ID: 1 │ │
│ │ TX: a1b2c3d4... │ │
│ │ Timestamp: 2025-12-17 10:30:00 │ │
│ │ [Ver Detalles] │ │
│ └──────────────────────────────────────┘ │
│ │
│ Registros HEAVY: │
│ ┌──────────────────────────────────────┐ │
│ │ ID: 1 │ │
│ │ TX: f6e5d4c3... │ │
│ │ Timestamp: 2025-12-17 10:30:01 │ │
│ │ [Ver Detalles] │ │
│ └──────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
Archivo: public/ver-json.html
Funcionalidad:
- Muestra detalles completos de un registro
- Pretty-print del JSON
- Información de blockchain (creator, signature)
- Metadatos de la transacción
Parámetros URL: ?tipo=light&txid=a1b2c3d4...
- CSS:
public/css/style.css,public/css/ver-json.css - JavaScript:
public/js/main.js,public/js/ver-json.js
Características:
- Diseño responsive
- Syntax highlighting para JSON
- Manejo de errores con UI feedback
- Navegación SPA (Single Page App)
Causa: La wallet no tiene la identidad importada.
Solución:
npm run importVerifica que existe: wallet/Admin@org1.example.com.id
Causas posibles:
- Test network no está corriendo
- Puertos bloqueados
- Perfil de conexión incorrecto
Solución:
# Verificar que test-network está activo
cd fabric-samples/test-network
./network.sh down
./network.sh up createChannel -c lightchannel
./network.sh up createChannel -c heavychannel
# Verificar conexión
docker ps | grep peerCausa: MySQL no está corriendo o credenciales incorrectas.
Solución:
# Verificar MySQL
mysql -u root -p
# Probar conexión con credenciales del .env
mysql -h 127.0.0.1 -P 3306 -u blockchain_user -p blockchain_hlfCausa: Chaincode no está deployado en el canal.
Solución:
cd fabric-samples/test-network
./network.sh deployCC -c lightchannel -ccn jsonstoragemodel -ccp ../chaincode/jsonstorage -ccl go
./network.sh deployCC -c heavychannel -ccn jsonstoragemodel -ccp ../chaincode/jsonstorage -ccl goCausa: Prometheus no configurado o no está scrapeando.
Solución:
- Verificar que Prometheus está corriendo:
http://localhost:9090 - Revisar targets:
http://localhost:9090/targets - Verificar que peers exponen métricas:
curl http://peer0.org1.example.com:9443/metrics
Habilitar logs detallados:
# En .env
NODE_ENV=development
DEBUG=fabric*
# Ejecutar
npm startVer logs de Fabric:
docker logs peer0.org1.example.com
docker logs orderer.example.com-
Nunca commitear el archivo
.env- Usa
.env.examplecomo plantilla - Añade
.enva.gitignore
- Usa
-
Rotar identidades periódicamente
- Regenerar certificados de wallet
- Usar HSM para producción
-
Validar inputs
- Sanitizar datos JSON antes de guardar
- Implementar rate limiting
-
Usar HTTPS en producción
const https = require('https'); const fs = require('fs'); const options = { key: fs.readFileSync('key.pem'), cert: fs.readFileSync('cert.pem') }; https.createServer(options, app).listen(443);
-
Usar pool de conexiones MySQL
- Ya implementado en el código
- Configurar
connectionLimitsegún carga
-
Cachear consultas frecuentes
const NodeCache = require('node-cache'); const cache = new NodeCache({ stdTTL: 60 });
-
Paginar resultados en
/registrosapp.get('/registros', async (req, res) => { const page = parseInt(req.query.page) || 1; const limit = 50; const offset = (page - 1) * limit; const [rows] = await pool.query( 'SELECT * FROM light_model_data LIMIT ? OFFSET ?', [limit, offset] ); // ... });
-
Monitorizar métricas de la API
- Usar
express-prom-bundlepara exponer métricas propias
- Usar
-
Backup regular de MySQL
mysqldump -u blockchain_user -p blockchain_hlf > backup_$(date +%Y%m%d).sql
-
Snapshots de blockchain
- Usar Fabric Snapshot para reducir ledger size
-
Limpiar registros antiguos
-- Archivar registros > 1 año DELETE FROM light_model_data WHERE timestamp < DATE_SUB(NOW(), INTERVAL 1 YEAR);
-
Monitorear tamaño de ledger
du -sh /var/hyperledger/production/ledgersData
-
Horizontal scaling de la API
- Usar load balancer (nginx, HAProxy)
- PM2 para clustering en Node.js
-
Separar canales por carga
- Crear canales adicionales según volumen
- Distribu