workspace-v1/core/orchestration/deployment/DEPLOYMENT-ARCHITECTURE.md
rckrdmrd 66161b1566 feat: Workspace-v1 complete migration with NEXUS v3.4
Sistema NEXUS v3.4 migrado con:

Estructura principal:
- core/orchestration: Sistema SIMCO + CAPVED (27 directivas, 28 perfiles)
- core/catalog: Catalogo de funcionalidades reutilizables
- shared/knowledge-base: Base de conocimiento compartida
- devtools/scripts: Herramientas de desarrollo
- control-plane/registries: Control de servicios y CI/CD
- orchestration/: Configuracion de orquestacion de agentes

Proyectos incluidos (11):
- gamilit (submodule -> GitHub)
- trading-platform (OrbiquanTIA)
- erp-suite con 5 verticales:
  - erp-core, construccion, vidrio-templado
  - mecanicas-diesel, retail, clinicas
- betting-analytics
- inmobiliaria-analytics
- platform_marketing_content
- pos-micro, erp-basico

Configuracion:
- .gitignore completo para Node.js/Python/Docker
- gamilit como submodule (git@github.com:rckrdmrd/gamilit-workspace.git)
- Sistema de puertos estandarizado (3005-3199)

Generated with NEXUS v3.4 Migration System
EPIC-010: Configuracion Git y Repositorios
2026-01-04 03:37:42 -06:00

42 KiB

Arquitectura de Despliegue - Workspace Multi-Proyecto

Resumen Ejecutivo

Aspecto Valor
Servidor Principal 72.60.226.4
Servidor Gamilit 74.208.126.102
Reverse Proxy Nginx
Registry Git Gitea (72.60.226.4:3000) / GitHub (gamilit)

Métodos de Despliegue por Servidor

Servidor Proyectos Método CI/CD Process Manager
72.60.226.4 trading-platform, erp-suite, pmc, betting, inmobiliaria Jenkins + Docker Docker Compose
74.208.126.102 gamilit Manual (git pull + PM2) PM2 Cluster

IMPORTANTE: Gamilit NO usa Jenkins. Se despliega manualmente con PM2.


1. Arquitectura de Servidores

┌─────────────────────────────────────────────────────────────────────────────┐
│                           SERVIDOR PRINCIPAL                                 │
│                            72.60.226.4                                       │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│  ┌─────────────┐    ┌─────────────────────────────────────────────────┐    │
│  │   NGINX     │    │              DOCKER CONTAINERS                   │    │
│  │  (Port 80)  │───>│                                                  │    │
│  │  (Port 443) │    │  ┌─────────────┐  ┌─────────────┐               │    │
│  └─────────────┘    │  │ trading-    │  │ erp-suite   │               │    │
│        │            │  │ platform    │  │ (verticales)│               │    │
│        │            │  │ FE:3080     │  │ FE:3010-3070│               │    │
│        │            │  │ BE:3081     │  │ BE:3011-3071│               │    │
│        │            │  └─────────────┘  └─────────────┘               │    │
│        │            │                                                  │    │
│        │            │  ┌─────────────┐  ┌─────────────┐               │    │
│        │            │  │ pmc         │  │ betting/    │               │    │
│        │            │  │ FE:3110     │  │ inmobiliaria│               │    │
│        │            │  │ BE:3111     │  │ (reservado) │               │    │
│        │            │  └─────────────┘  └─────────────┘               │    │
│        │            └─────────────────────────────────────────────────┘    │
│        │                                                                    │
│  ┌─────────────┐    ┌─────────────────────────────────────────────────┐    │
│  │  JENKINS    │    │              INFRAESTRUCTURA                     │    │
│  │  (Port 8080)│    │  PostgreSQL: 5432  │  Redis: 6379               │    │
│  └─────────────┘    │  Gitea: 3000       │  Registry: 5000            │    │
│                     └─────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────────────┐
│                           SERVIDOR GAMILIT                                   │
│                           74.208.126.102                                     │
├─────────────────────────────────────────────────────────────────────────────┤
│  ┌─────────────┐    ┌─────────────────────────────────────────────────┐    │
│  │   NGINX     │    │                 PM2 CLUSTER                      │    │
│  │  (Port 80)  │───>│  Backend:  2 instances (Port 3006)              │    │
│  │  (Port 443) │    │  Frontend: 1 instance  (Port 3005)              │    │
│  └─────────────┘    └─────────────────────────────────────────────────┘    │
│                                                                             │
│  ┌─────────────────────────────────────────────────────────────────────┐    │
│  │  PostgreSQL: 5432 (gamilit_platform)                                │    │
│  └─────────────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────────────┘

2. Estructura de Repositorios

2.1 Repositorios Independientes por Proyecto

Proyecto Repositorio Servidor Deploy
gamilit github.com/rckrdmrd/gamilit-workspace.git 74.208.126.102
trading-platform 72.60.226.4:3000/rckrdmrd/trading-platform.git 72.60.226.4
erp-suite 72.60.226.4:3000/rckrdmrd/erp-suite.git 72.60.226.4
platform-marketing-content 72.60.226.4:3000/rckrdmrd/pmc.git 72.60.226.4
betting-analytics 72.60.226.4:3000/rckrdmrd/betting-analytics.git 72.60.226.4
inmobiliaria-analytics 72.60.226.4:3000/rckrdmrd/inmobiliaria-analytics.git 72.60.226.4

2.2 Estructura Interna de Cada Repositorio

proyecto/
├── apps/
│   ├── backend/
│   │   ├── src/
│   │   ├── Dockerfile
│   │   ├── package.json
│   │   ├── .env.example
│   │   └── .env.production
│   └── frontend/
│       ├── src/
│       ├── Dockerfile
│       ├── nginx.conf
│       ├── package.json
│       ├── .env.example
│       └── .env.production
├── database/
│   ├── schemas/
│   ├── seeds/
│   └── migrations/
├── docker/
│   ├── docker-compose.yml
│   ├── docker-compose.prod.yml
│   └── .env.docker
├── jenkins/
│   └── Jenkinsfile
├── nginx/
│   └── project.conf
├── scripts/
│   ├── deploy.sh
│   ├── rollback.sh
│   └── health-check.sh
├── .env.ports
└── README.md

3. Asignación de Subdominios

3.1 Servidor Principal (72.60.226.4)

Subdominio Proyecto Frontend Backend API
trading.isem.dev trading-platform 3080 3081
api.trading.isem.dev trading-platform - 3081
erp.isem.dev erp-core 3010 3011
api.erp.isem.dev erp-core - 3011
construccion.erp.isem.dev construccion 3020 3021
vidrio.erp.isem.dev vidrio-templado 3030 3031
mecanicas.erp.isem.dev mecanicas-diesel 3040 3041
retail.erp.isem.dev retail 3050 3051
clinicas.erp.isem.dev clinicas 3060 3061
pos.erp.isem.dev pos-micro 3070 3071
pmc.isem.dev platform-marketing-content 3110 3111
api.pmc.isem.dev platform-marketing-content - 3111
betting.isem.dev betting-analytics 3090 3091
inmobiliaria.isem.dev inmobiliaria-analytics 3100 3101

3.2 Servidor Gamilit (74.208.126.102)

Subdominio Proyecto Frontend Backend API
gamilit.com gamilit 3005 3006
api.gamilit.com gamilit - 3006
app.gamilit.com gamilit 3005 -

4. Configuración Nginx

4.1 Nginx Principal (72.60.226.4)

Archivo: /etc/nginx/nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    multi_accept on;
    use epoll;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Logging
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log main;

    # Performance
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 100M;

    # Gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml application/json application/javascript
               application/xml application/xml+rss text/javascript application/x-javascript;

    # Rate Limiting
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # Upstreams
    include /etc/nginx/upstreams/*.conf;

    # Virtual Hosts
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

4.2 Upstreams por Proyecto

Archivo: /etc/nginx/upstreams/projects.conf

# =============================================================================
# TRADING PLATFORM
# =============================================================================
upstream trading_frontend {
    server 127.0.0.1:3080;
    keepalive 32;
}

upstream trading_backend {
    server 127.0.0.1:3081;
    keepalive 32;
}

upstream trading_websocket {
    server 127.0.0.1:3082;
    keepalive 32;
}

# =============================================================================
# ERP SUITE
# =============================================================================
upstream erp_core_frontend {
    server 127.0.0.1:3010;
    keepalive 32;
}

upstream erp_core_backend {
    server 127.0.0.1:3011;
    keepalive 32;
}

upstream erp_construccion_frontend {
    server 127.0.0.1:3020;
    keepalive 32;
}

upstream erp_construccion_backend {
    server 127.0.0.1:3021;
    keepalive 32;
}

upstream erp_vidrio_frontend {
    server 127.0.0.1:3030;
    keepalive 32;
}

upstream erp_vidrio_backend {
    server 127.0.0.1:3031;
    keepalive 32;
}

upstream erp_mecanicas_frontend {
    server 127.0.0.1:3040;
    keepalive 32;
}

upstream erp_mecanicas_backend {
    server 127.0.0.1:3041;
    keepalive 32;
}

upstream erp_retail_frontend {
    server 127.0.0.1:3050;
    keepalive 32;
}

upstream erp_retail_backend {
    server 127.0.0.1:3051;
    keepalive 32;
}

upstream erp_clinicas_frontend {
    server 127.0.0.1:3060;
    keepalive 32;
}

upstream erp_clinicas_backend {
    server 127.0.0.1:3061;
    keepalive 32;
}

upstream erp_pos_frontend {
    server 127.0.0.1:3070;
    keepalive 32;
}

upstream erp_pos_backend {
    server 127.0.0.1:3071;
    keepalive 32;
}

# =============================================================================
# PLATFORM MARKETING CONTENT
# =============================================================================
upstream pmc_frontend {
    server 127.0.0.1:3110;
    keepalive 32;
}

upstream pmc_backend {
    server 127.0.0.1:3111;
    keepalive 32;
}

# =============================================================================
# BETTING ANALYTICS (RESERVADO)
# =============================================================================
upstream betting_frontend {
    server 127.0.0.1:3090;
    keepalive 32;
}

upstream betting_backend {
    server 127.0.0.1:3091;
    keepalive 32;
}

# =============================================================================
# INMOBILIARIA ANALYTICS (RESERVADO)
# =============================================================================
upstream inmobiliaria_frontend {
    server 127.0.0.1:3100;
    keepalive 32;
}

upstream inmobiliaria_backend {
    server 127.0.0.1:3101;
    keepalive 32;
}

4.3 Virtual Host Template

Archivo: /etc/nginx/conf.d/trading.conf

# =============================================================================
# TRADING PLATFORM - trading.isem.dev
# =============================================================================

# HTTP -> HTTPS redirect
server {
    listen 80;
    server_name trading.isem.dev api.trading.isem.dev;
    return 301 https://$server_name$request_uri;
}

# Frontend
server {
    listen 443 ssl http2;
    server_name trading.isem.dev;

    # SSL Configuration
    ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Security Headers
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';" always;

    # Frontend proxy
    location / {
        proxy_pass http://trading_frontend;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_cache_bypass $http_upgrade;
    }

    # Static assets caching
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://trading_frontend;
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # WebSocket
    location /ws {
        proxy_pass http://trading_websocket;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }
}

# Backend API
server {
    listen 443 ssl http2;
    server_name api.trading.isem.dev;

    ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    # Rate limiting
    limit_req zone=api_limit burst=20 nodelay;
    limit_conn conn_limit 10;

    # API proxy
    location / {
        proxy_pass http://trading_backend;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # CORS headers
        add_header Access-Control-Allow-Origin $http_origin always;
        add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
        add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With" always;
        add_header Access-Control-Allow-Credentials "true" always;

        if ($request_method = OPTIONS) {
            return 204;
        }
    }

    # Health check endpoint
    location /health {
        proxy_pass http://trading_backend/health;
        access_log off;
    }
}

5. Despliegue con PM2 (Gamilit)

IMPORTANTE: Gamilit NO usa Jenkins ni Docker. Usa PM2 como gestor de procesos.

5.1 Configuración PM2 (74.208.126.102)

Archivo: ecosystem.config.js

module.exports = {
  apps: [
    {
      name: 'gamilit-backend',
      cwd: './apps/backend',
      script: 'dist/main.js',
      instances: 2,              // Cluster mode con 2 instancias
      exec_mode: 'cluster',
      env_production: {
        NODE_ENV: 'production',
        PORT: 3006,
      },
      max_memory_restart: '1G',
      error_file: '../../logs/backend-error.log',
      out_file: '../../logs/backend-out.log',
    },
    {
      name: 'gamilit-frontend',
      cwd: './apps/frontend',
      script: 'npx',
      args: 'vite preview --port 3005 --host 0.0.0.0',
      instances: 1,
      exec_mode: 'fork',
      env_production: {
        NODE_ENV: 'production',
      },
      max_memory_restart: '512M',
    },
  ],
};

5.2 Comandos de Despliegue Gamilit

# Conectar al servidor
ssh isem@74.208.126.102

# Ir al directorio del proyecto
cd /home/isem/workspace/workspace-gamilit/gamilit/projects/gamilit

# Pull cambios
git pull origin main

# Instalar dependencias
npm install

# Build backend y frontend
npm run build:all

# Reiniciar con PM2
pm2 reload ecosystem.config.js --env production

# Guardar configuración
pm2 save

# Verificar estado
pm2 status
pm2 logs

5.3 Script de Despliegue Automatizado

Archivo: apps/devops/scripts/deploy.sh

./deploy.sh --env prod           # Despliegue completo
./deploy.sh --env prod --skip-db # Sin reiniciar BD
./deploy.sh --env prod --dry-run # Simular despliegue

6. Jenkins Pipeline (Solo 72.60.226.4)

NOTA: Esta sección solo aplica para proyectos en el servidor 72.60.226.4. Gamilit usa PM2 (ver sección 5).

6.1 Estructura Jenkins

/var/jenkins_home/
├── jobs/
│   ├── trading-platform/
│   │   ├── backend/
│   │   └── frontend/
│   ├── erp-suite/
│   │   ├── erp-core/
│   │   ├── construccion/
│   │   ├── vidrio-templado/
│   │   └── ...
│   └── pmc/
│       ├── backend/
│       └── frontend/
├── shared-libraries/
│   └── vars/
│       ├── deployNode.groovy
│       ├── deployDocker.groovy
│       └── notifySlack.groovy
└── credentials/
    ├── ssh-keys/
    └── docker-registry/

6.2 Jenkinsfile Template

Archivo: jenkins/Jenkinsfile

pipeline {
    agent any

    environment {
        PROJECT_NAME = 'trading-platform'
        DOCKER_REGISTRY = '72.60.226.4:5000'
        DEPLOY_SERVER = '72.60.226.4'
        DEPLOY_USER = 'deploy'

        // Credenciales
        DOCKER_CREDENTIALS = credentials('docker-registry')
        SSH_KEY = credentials('deploy-ssh-key')

        // Versión
        VERSION = "${env.BUILD_NUMBER}"
        GIT_COMMIT_SHORT = sh(script: "git rev-parse --short HEAD", returnStdout: true).trim()
    }

    options {
        timeout(time: 30, unit: 'MINUTES')
        disableConcurrentBuilds()
        buildDiscarder(logRotator(numToKeepStr: '10'))
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
                script {
                    env.GIT_BRANCH = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
                }
            }
        }

        stage('Install Dependencies') {
            parallel {
                stage('Backend') {
                    steps {
                        dir('apps/backend') {
                            sh 'npm ci'
                        }
                    }
                }
                stage('Frontend') {
                    steps {
                        dir('apps/frontend') {
                            sh 'npm ci'
                        }
                    }
                }
            }
        }

        stage('Lint & Test') {
            parallel {
                stage('Backend Lint') {
                    steps {
                        dir('apps/backend') {
                            sh 'npm run lint'
                        }
                    }
                }
                stage('Backend Test') {
                    steps {
                        dir('apps/backend') {
                            sh 'npm run test'
                        }
                    }
                }
                stage('Frontend Lint') {
                    steps {
                        dir('apps/frontend') {
                            sh 'npm run lint'
                        }
                    }
                }
                stage('Frontend Test') {
                    steps {
                        dir('apps/frontend') {
                            sh 'npm run test'
                        }
                    }
                }
            }
        }

        stage('Build') {
            parallel {
                stage('Build Backend') {
                    steps {
                        dir('apps/backend') {
                            sh 'npm run build'
                        }
                    }
                }
                stage('Build Frontend') {
                    steps {
                        dir('apps/frontend') {
                            sh 'npm run build'
                        }
                    }
                }
            }
        }

        stage('Docker Build & Push') {
            when {
                anyOf {
                    branch 'main'
                    branch 'develop'
                }
            }
            parallel {
                stage('Backend Image') {
                    steps {
                        dir('apps/backend') {
                            script {
                                def image = docker.build("${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION}")
                                docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
                                    image.push()
                                    image.push('latest')
                                }
                            }
                        }
                    }
                }
                stage('Frontend Image') {
                    steps {
                        dir('apps/frontend') {
                            script {
                                def image = docker.build("${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION}")
                                docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
                                    image.push()
                                    image.push('latest')
                                }
                            }
                        }
                    }
                }
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'develop'
            }
            steps {
                script {
                    deployToServer('staging')
                }
            }
        }

        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            steps {
                input message: 'Deploy to Production?', ok: 'Deploy'
                script {
                    deployToServer('production')
                }
            }
        }

        stage('Health Check') {
            steps {
                script {
                    def healthUrl = env.GIT_BRANCH == 'main'
                        ? "https://api.trading.isem.dev/health"
                        : "https://staging.api.trading.isem.dev/health"

                    retry(3) {
                        sleep(time: 10, unit: 'SECONDS')
                        sh "curl -f ${healthUrl} || exit 1"
                    }
                }
            }
        }
    }

    post {
        success {
            slackSend(
                color: 'good',
                message: "✅ ${PROJECT_NAME} deployed successfully! Build #${BUILD_NUMBER}"
            )
        }
        failure {
            slackSend(
                color: 'danger',
                message: "❌ ${PROJECT_NAME} deployment failed! Build #${BUILD_NUMBER}"
            )
        }
        always {
            cleanWs()
        }
    }
}

def deployToServer(String environment) {
    def composeFile = environment == 'production'
        ? 'docker/docker-compose.prod.yml'
        : 'docker/docker-compose.staging.yml'

    sshagent(['deploy-ssh-key']) {
        sh """
            ssh -o StrictHostKeyChecking=no ${DEPLOY_USER}@${DEPLOY_SERVER} '
                cd /opt/apps/${PROJECT_NAME}
                docker-compose -f ${composeFile} pull
                docker-compose -f ${composeFile} up -d
                docker system prune -f
            '
        """
    }
}

6.3 Pipeline para ERP-Suite (Multi-Vertical)

Archivo: erp-suite/jenkins/Jenkinsfile

pipeline {
    agent any

    parameters {
        choice(
            name: 'VERTICAL',
            choices: ['erp-core', 'construccion', 'vidrio-templado', 'mecanicas-diesel', 'retail', 'clinicas', 'pos-micro', 'all'],
            description: 'Select vertical to deploy'
        )
        choice(
            name: 'ENVIRONMENT',
            choices: ['staging', 'production'],
            description: 'Target environment'
        )
    }

    environment {
        DOCKER_REGISTRY = '72.60.226.4:5000'
        DEPLOY_SERVER = '72.60.226.4'
    }

    stages {
        stage('Determine Verticals') {
            steps {
                script {
                    if (params.VERTICAL == 'all') {
                        env.VERTICALS = 'erp-core,construccion,vidrio-templado,mecanicas-diesel,retail,clinicas,pos-micro'
                    } else {
                        env.VERTICALS = params.VERTICAL
                    }
                }
            }
        }

        stage('Build & Deploy Verticals') {
            steps {
                script {
                    def verticals = env.VERTICALS.split(',')
                    def parallelStages = [:]

                    verticals.each { vertical ->
                        parallelStages[vertical] = {
                            stage("Build ${vertical}") {
                                dir("apps/${getVerticalPath(vertical)}") {
                                    sh 'npm ci && npm run build'
                                }
                            }
                            stage("Docker ${vertical}") {
                                dir("apps/${getVerticalPath(vertical)}") {
                                    def backendImage = docker.build("${DOCKER_REGISTRY}/erp-${vertical}-backend:${BUILD_NUMBER}")
                                    def frontendImage = docker.build("${DOCKER_REGISTRY}/erp-${vertical}-frontend:${BUILD_NUMBER}")

                                    docker.withRegistry("https://${DOCKER_REGISTRY}", 'docker-registry') {
                                        backendImage.push()
                                        frontendImage.push()
                                    }
                                }
                            }
                        }
                    }

                    parallel parallelStages
                }
            }
        }

        stage('Deploy') {
            steps {
                script {
                    def verticals = env.VERTICALS.split(',')
                    verticals.each { vertical ->
                        deployVertical(vertical, params.ENVIRONMENT)
                    }
                }
            }
        }
    }
}

def getVerticalPath(String vertical) {
    if (vertical == 'erp-core') return 'erp-core'
    if (vertical == 'pos-micro') return 'products/pos-micro'
    return "verticales/${vertical}"
}

def deployVertical(String vertical, String environment) {
    def ports = getVerticalPorts(vertical)

    sshagent(['deploy-ssh-key']) {
        sh """
            ssh deploy@${DEPLOY_SERVER} '
                cd /opt/apps/erp-suite/${vertical}
                docker-compose -f docker-compose.${environment}.yml pull
                docker-compose -f docker-compose.${environment}.yml up -d
            '
        """
    }
}

def getVerticalPorts(String vertical) {
    def portMap = [
        'erp-core': [fe: 3010, be: 3011],
        'construccion': [fe: 3020, be: 3021],
        'vidrio-templado': [fe: 3030, be: 3031],
        'mecanicas-diesel': [fe: 3040, be: 3041],
        'retail': [fe: 3050, be: 3051],
        'clinicas': [fe: 3060, be: 3061],
        'pos-micro': [fe: 3070, be: 3071]
    ]
    return portMap[vertical]
}

7. Variables de Entorno

7.1 Estructura por Ambiente

.env.development    # Desarrollo local
.env.staging        # Ambiente de pruebas
.env.production     # Producción
.env.example        # Template con placeholders

7.2 Template Variables Backend

Archivo: .env.production.template

# =============================================================================
# [PROJECT_NAME] Backend - Production Environment
# =============================================================================

# Application
NODE_ENV=production
PORT=${BACKEND_PORT}
API_PREFIX=api
API_VERSION=v1

# Server
SERVER_URL=https://api.${SUBDOMAIN}.isem.dev
FRONTEND_URL=https://${SUBDOMAIN}.isem.dev

# Database
DB_HOST=${DB_HOST:-localhost}
DB_PORT=${DB_PORT:-5432}
DB_NAME=${DB_NAME}
DB_USER=${DB_USER}
DB_PASSWORD=${DB_PASSWORD}
DB_SSL=true
DB_POOL_MAX=20

# Redis
REDIS_HOST=${REDIS_HOST:-localhost}
REDIS_PORT=${REDIS_PORT:-6379}
REDIS_PASSWORD=${REDIS_PASSWORD}

# JWT (CHANGE IN PRODUCTION!)
JWT_SECRET=${JWT_SECRET}
JWT_EXPIRES_IN=15m
JWT_REFRESH_SECRET=${JWT_REFRESH_SECRET}
JWT_REFRESH_EXPIRES_IN=7d

# CORS
CORS_ORIGIN=https://${SUBDOMAIN}.isem.dev

# Security
ENABLE_SWAGGER=false
RATE_LIMIT_WINDOW_MS=60000
RATE_LIMIT_MAX=100

# Logging
LOG_LEVEL=warn
LOG_TO_FILE=true
LOG_FILE_PATH=/var/log/${PROJECT_NAME}/app.log

7.3 Template Variables Frontend

Archivo: .env.production.template

# =============================================================================
# [PROJECT_NAME] Frontend - Production Environment
# =============================================================================

# Application
VITE_APP_NAME=${PROJECT_NAME}
VITE_APP_VERSION=${VERSION}
VITE_APP_ENV=production

# API Configuration
VITE_API_URL=https://api.${SUBDOMAIN}.isem.dev
VITE_API_PREFIX=api/v1
VITE_API_TIMEOUT=30000

# WebSocket
VITE_WS_URL=wss://api.${SUBDOMAIN}.isem.dev/ws

# Features
VITE_ENABLE_DEBUG=false
VITE_ENABLE_ANALYTICS=true
VITE_LOG_LEVEL=error

# External Services
VITE_SENTRY_DSN=${SENTRY_DSN}
VITE_GOOGLE_ANALYTICS_ID=${GA_ID}

7.4 Matriz de Variables por Proyecto

Variable gamilit trading erp-core pmc
BACKEND_PORT 3006 3081 3011 3111
FRONTEND_PORT 3005 3080 3010 3110
DB_NAME gamilit_platform orbiquant_platform erp_generic pmc_dev
DB_USER gamilit_user orbiquant_user erp_admin pmc_user
SUBDOMAIN gamilit.com trading.isem.dev erp.isem.dev pmc.isem.dev
SERVER 74.208.126.102 72.60.226.4 72.60.226.4 72.60.226.4

8. Docker Compose Producción

Archivo: docker/docker-compose.prod.yml

version: '3.8'

services:
  backend:
    image: ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION:-latest}
    container_name: ${PROJECT_NAME}-backend
    restart: unless-stopped
    ports:
      - "${BACKEND_PORT}:${BACKEND_PORT}"
    environment:
      - NODE_ENV=production
    env_file:
      - ../apps/backend/.env.production
    volumes:
      - backend-logs:/var/log/${PROJECT_NAME}
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:${BACKEND_PORT}/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - app-network
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

  frontend:
    image: ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION:-latest}
    container_name: ${PROJECT_NAME}-frontend
    restart: unless-stopped
    ports:
      - "${FRONTEND_PORT}:80"
    depends_on:
      - backend
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:80"]
      interval: 30s
      timeout: 10s
      retries: 3
    networks:
      - app-network
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 128M

volumes:
  backend-logs:

networks:
  app-network:
    external: true
    name: isem-network

9. Scripts de Despliegue

9.1 Script de Deploy Automatizado

Archivo: scripts/deploy.sh

#!/bin/bash
# =============================================================================
# Deploy Script - Multi-Project
# =============================================================================

set -e

# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# Configuration
PROJECT_NAME="${PROJECT_NAME:-}"
ENVIRONMENT="${ENVIRONMENT:-production}"
VERSION="${VERSION:-latest}"
DEPLOY_SERVER="${DEPLOY_SERVER:-72.60.226.4}"
DOCKER_REGISTRY="${DOCKER_REGISTRY:-72.60.226.4:5000}"

# Functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }

check_prerequisites() {
    log_info "Checking prerequisites..."

    command -v docker >/dev/null 2>&1 || { log_error "Docker not installed"; exit 1; }
    command -v ssh >/dev/null 2>&1 || { log_error "SSH not available"; exit 1; }

    if [ -z "$PROJECT_NAME" ]; then
        log_error "PROJECT_NAME not set"
        exit 1
    fi
}

build_images() {
    log_info "Building Docker images..."

    # Backend
    docker build -t ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION} \
        -f apps/backend/Dockerfile apps/backend/

    # Frontend
    docker build -t ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION} \
        -f apps/frontend/Dockerfile apps/frontend/
}

push_images() {
    log_info "Pushing images to registry..."

    docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION}
    docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION}

    # Tag as latest
    docker tag ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:${VERSION} \
        ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:latest
    docker tag ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:${VERSION} \
        ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:latest

    docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-backend:latest
    docker push ${DOCKER_REGISTRY}/${PROJECT_NAME}-frontend:latest
}

deploy_to_server() {
    log_info "Deploying to ${DEPLOY_SERVER}..."

    ssh deploy@${DEPLOY_SERVER} << EOF
        cd /opt/apps/${PROJECT_NAME}

        # Pull latest images
        docker-compose -f docker-compose.${ENVIRONMENT}.yml pull

        # Stop current containers
        docker-compose -f docker-compose.${ENVIRONMENT}.yml down

        # Start new containers
        docker-compose -f docker-compose.${ENVIRONMENT}.yml up -d

        # Cleanup
        docker system prune -f

        # Health check
        sleep 10
        curl -f http://localhost:${BACKEND_PORT}/health || exit 1
EOF
}

rollback() {
    log_warn "Rolling back to previous version..."

    ssh deploy@${DEPLOY_SERVER} << EOF
        cd /opt/apps/${PROJECT_NAME}
        docker-compose -f docker-compose.${ENVIRONMENT}.yml down

        # Use previous tag
        sed -i 's/:latest/:previous/g' docker-compose.${ENVIRONMENT}.yml
        docker-compose -f docker-compose.${ENVIRONMENT}.yml up -d
EOF
}

# Main
main() {
    check_prerequisites

    case "$1" in
        build)
            build_images
            ;;
        push)
            push_images
            ;;
        deploy)
            deploy_to_server
            ;;
        full)
            build_images
            push_images
            deploy_to_server
            ;;
        rollback)
            rollback
            ;;
        *)
            echo "Usage: $0 {build|push|deploy|full|rollback}"
            exit 1
            ;;
    esac
}

main "$@"

9.2 Script de Configuración de Nginx

Archivo: scripts/setup-nginx.sh

#!/bin/bash
# =============================================================================
# Setup Nginx for Multi-Project
# =============================================================================

set -e

NGINX_CONF_DIR="/etc/nginx"
PROJECTS_CONF_DIR="${NGINX_CONF_DIR}/conf.d"
UPSTREAMS_DIR="${NGINX_CONF_DIR}/upstreams"

# Project configurations
declare -A PROJECTS=(
    ["trading"]="3080:3081:3082"
    ["erp-core"]="3010:3011"
    ["construccion"]="3020:3021"
    ["vidrio"]="3030:3031"
    ["mecanicas"]="3040:3041"
    ["retail"]="3050:3051"
    ["clinicas"]="3060:3061"
    ["pos"]="3070:3071"
    ["pmc"]="3110:3111"
    ["betting"]="3090:3091"
    ["inmobiliaria"]="3100:3101"
)

create_upstream() {
    local name=$1
    local ports=$2

    IFS=':' read -ra PORT_ARRAY <<< "$ports"
    local fe_port=${PORT_ARRAY[0]}
    local be_port=${PORT_ARRAY[1]}
    local ws_port=${PORT_ARRAY[2]:-}

    cat > "${UPSTREAMS_DIR}/${name}.conf" << EOF
upstream ${name}_frontend {
    server 127.0.0.1:${fe_port};
    keepalive 32;
}

upstream ${name}_backend {
    server 127.0.0.1:${be_port};
    keepalive 32;
}
EOF

    if [ -n "$ws_port" ]; then
        cat >> "${UPSTREAMS_DIR}/${name}.conf" << EOF

upstream ${name}_websocket {
    server 127.0.0.1:${ws_port};
    keepalive 32;
}
EOF
    fi
}

create_vhost() {
    local name=$1
    local subdomain="${name}.isem.dev"

    cat > "${PROJECTS_CONF_DIR}/${name}.conf" << EOF
server {
    listen 80;
    server_name ${subdomain} api.${subdomain};
    return 301 https://\$server_name\$request_uri;
}

server {
    listen 443 ssl http2;
    server_name ${subdomain};

    ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem;

    location / {
        proxy_pass http://${name}_frontend;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
}

server {
    listen 443 ssl http2;
    server_name api.${subdomain};

    ssl_certificate /etc/letsencrypt/live/isem.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/isem.dev/privkey.pem;

    location / {
        proxy_pass http://${name}_backend;
        proxy_http_version 1.1;
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;
    }
}
EOF
}

# Main
mkdir -p "${UPSTREAMS_DIR}"

for project in "${!PROJECTS[@]}"; do
    echo "Configuring ${project}..."
    create_upstream "$project" "${PROJECTS[$project]}"
    create_vhost "$project"
done

# Test configuration
nginx -t

# Reload
systemctl reload nginx

echo "Nginx configured successfully!"

10. Checklist de Implementación

Fase 1: Infraestructura Base (Servidor 72.60.226.4)

  • Instalar Docker y Docker Compose
  • Configurar Docker Registry privado (puerto 5000)
  • Instalar y configurar Jenkins
  • Instalar Nginx
  • Obtener certificados SSL (Let's Encrypt wildcard para *.isem.dev)
  • Configurar red Docker compartida (isem-network)
  • Crear directorios base /opt/apps/{proyecto}

Fase 2: Separación de Repositorios

  • Crear repositorios en Gitea para cada proyecto
  • Migrar código de monorepo a repos individuales
  • Configurar webhooks de Gitea a Jenkins
  • Actualizar referencias de git remotes

Fase 3: CI/CD Jenkins

  • Crear jobs de Jenkins por proyecto
  • Configurar credenciales (SSH, Docker Registry)
  • Configurar shared libraries
  • Probar pipelines en ambiente staging

Fase 4: Nginx y DNS

  • Configurar upstreams por proyecto
  • Crear virtual hosts
  • Configurar DNS para subdominios
  • Verificar SSL/TLS

Fase 5: Despliegue

  • Desplegar trading-platform
  • Desplegar erp-suite (por verticales)
  • Desplegar platform-marketing-content
  • Verificar health checks
  • Configurar monitoreo

11. Referencias

  • Inventario de Puertos: /home/isem/workspace/core/orchestration/inventarios/DEVENV-PORTS-INVENTORY.yml
  • Inventario de Despliegue: /home/isem/workspace/core/orchestration/inventarios/DEPLOYMENT-INVENTORY.yml
  • Gitea (72.60.226.4): http://72.60.226.4:3000

Gamilit (PM2)

  • Servidor: 74.208.126.102
  • Repositorio: https://github.com/rckrdmrd/gamilit-workspace.git
  • ecosystem.config.js: /home/isem/workspace/projects/gamilit/ecosystem.config.js
  • Deploy Script: /home/isem/workspace/projects/gamilit/apps/devops/scripts/deploy.sh
  • Documentación: /home/isem/workspace/projects/gamilit/README.md

Documento generado por DevEnv Agent - 2025-12-12 Versión: 1.0.0