# DEPLOYMENT GUIDE - ERP Generic

**Última actualización:** 2025-11-24
**Responsable:** DevOps Team
**Estado:** ✅ Production-Ready

---

## TABLE OF CONTENTS

1. [Overview](#1-overview)
2. [Prerequisites](#2-prerequisites)
3. [Docker Setup](#3-docker-setup)
4. [PostgreSQL 16 Setup](#4-postgresql-16-setup)
5. [Redis Setup](#5-redis-setup)
6. [Environment Variables](#6-environment-variables)
7. [Multi-Environment Strategy](#7-multi-environment-strategy)
8. [Deployment Steps](#8-deployment-steps)
9. [Zero-Downtime Deployment](#9-zero-downtime-deployment)
10. [Rollback Procedures](#10-rollback-procedures)
11. [Troubleshooting](#11-troubleshooting)
12. [References](#12-references)

---

## 1. OVERVIEW

### 1.1 System Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                    Load Balancer / Nginx                    │
│                    (SSL Termination)                         │
└────────────┬────────────────────────────────┬───────────────┘
             │                                │
     ┌───────▼───────┐                ┌──────▼──────┐
     │   Frontend    │                │   Backend   │
     │   React 18    │                │  NestJS 10  │
     │   Vite 5      │                │  Prisma 5   │
     └───────────────┘                └──────┬──────┘
                                             │
                         ┌───────────────────┼────────────────┐
                         │                   │                │
                  ┌──────▼──────┐    ┌──────▼─────┐   ┌──────▼──────┐
                  │ PostgreSQL  │    │   Redis    │   │    S3       │
                  │     16      │    │     7      │   │  (Storage)  │
                  │  9 schemas  │    │  (Cache)   │   │             │
                  └─────────────┘    └────────────┘   └─────────────┘
```

### 1.2 Components

| Component | Version | Purpose | Port |
|-----------|---------|---------|------|
| **Backend** | NestJS 10 + Node.js 20 | REST API + Business Logic | 3000 |
| **Frontend** | React 18 + Vite 5 | Web UI | 5173 |
| **PostgreSQL** | 16-alpine | Primary Database (9 schemas) | 5432 |
| **Redis** | 7-alpine | Session store + Cache | 6379 |
| **Nginx** | 1.25-alpine | Reverse proxy + SSL | 80, 443 |

### 1.3 System Requirements

**Minimum (Development):**
- CPU: 4 cores
- RAM: 8 GB
- Disk: 50 GB SSD
- OS: Linux (Ubuntu 22.04 LTS recommended)

**Recommended (Production):**
- CPU: 8+ cores
- RAM: 16+ GB
- Disk: 200+ GB SSD (NVMe preferred)
- OS: Ubuntu 22.04 LTS / RHEL 9 / Amazon Linux 2023

**Scalability (High Load):**
- CPU: 16+ cores
- RAM: 32+ GB
- Disk: 500+ GB SSD NVMe
- Load Balancer: AWS ELB / Azure Load Balancer / Nginx
- Database: PostgreSQL 16 with read replicas

---

## 2. PREREQUISITES

### 2.1 Software Requirements

```bash
# Docker & Docker Compose
docker --version  # >= 24.0
docker-compose --version  # >= 2.20

# Git
git --version  # >= 2.40

# Node.js (for local development)
node --version  # >= 20.0 LTS
npm --version   # >= 10.0

# PostgreSQL Client (for manual operations)
psql --version  # >= 16.0

# Redis Client (for manual operations)
redis-cli --version  # >= 7.0
```

### 2.2 Installation (Ubuntu 22.04)

```bash
# Update system
sudo apt update && sudo apt upgrade -y

# Install Docker
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker $USER
newgrp docker

# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Install PostgreSQL client
sudo apt install -y postgresql-client-16

# Install Redis client
sudo apt install -y redis-tools

# Install Git
sudo apt install -y git

# Verify installations
docker --version
docker-compose --version
psql --version
redis-cli --version
git --version
```

### 2.3 Network Requirements

**Firewall Rules (Inbound):**
- Port 80 (HTTP) - Allow from Load Balancer only
- Port 443 (HTTPS) - Allow from Load Balancer only
- Port 22 (SSH) - Allow from trusted IPs only (bastion host)
- Port 3000 (Backend API) - Internal only
- Port 5432 (PostgreSQL) - Internal only
- Port 6379 (Redis) - Internal only

**Firewall Rules (Outbound):**
- Port 80, 443 (HTTPS) - Allow (for package downloads, API calls)
- Port 25, 587 (SMTP) - Allow (for email notifications)

**DNS Records:**
```
# Production
erp-generic.com          A     <LOAD_BALANCER_IP>
api.erp-generic.com      A     <LOAD_BALANCER_IP>
*.erp-generic.com        A     <LOAD_BALANCER_IP>  # Multi-tenant subdomains

# Staging
staging.erp-generic.com  A     <STAGING_IP>

# QA
qa.erp-generic.local     A     <QA_IP>
```

---

## 3. DOCKER SETUP

### 3.1 Directory Structure

```
erp-generic/
├── backend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── src/
│   ├── prisma/
│   └── package.json
├── frontend/
│   ├── Dockerfile
│   ├── .dockerignore
│   ├── src/
│   └── package.json
├── docker-compose.yml
├── docker-compose.prod.yml
├── docker-compose.dev.yml
├── .env.example
└── nginx/
    └── nginx.conf
```

### 3.2 Backend Dockerfile

**File:** `backend/Dockerfile`

```dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./
COPY prisma ./prisma/

# Install dependencies (including devDependencies for build)
RUN npm ci

# Copy source code
COPY . .

# Generate Prisma Client
RUN npx prisma generate

# Build TypeScript
RUN npm run build

# Stage 2: Production
FROM node:20-alpine AS production

WORKDIR /app

# Install only production dependencies
COPY package*.json ./
COPY prisma ./prisma/
RUN npm ci --only=production

# Copy built files from builder stage
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/node_modules/.prisma ./node_modules/.prisma

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nestjs -u 1001
USER nestjs

# Expose port
EXPOSE 3000

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=40s --retries=3 \
  CMD node -e "require('http').get('http://localhost:3000/health', (res) => { process.exit(res.statusCode === 200 ? 0 : 1); });"

# Start application
CMD ["node", "dist/main.js"]
```

### 3.3 Frontend Dockerfile

**File:** `frontend/Dockerfile`

```dockerfile
# Stage 1: Build
FROM node:20-alpine AS builder

WORKDIR /app

# Copy package files
COPY package*.json ./

# Install dependencies
RUN npm ci

# Copy source code
COPY . .

# Build application
RUN npm run build

# Stage 2: Production with Nginx
FROM nginx:1.25-alpine AS production

# Copy built files
COPY --from=builder /app/dist /usr/share/nginx/html

# Copy custom nginx config
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Expose port
EXPOSE 80

# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --quiet --tries=1 --spider http://localhost:80/ || exit 1

# Start nginx
CMD ["nginx", "-g", "daemon off;"]
```

**File:** `frontend/nginx.conf`

```nginx
server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # 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 "no-referrer-when-downgrade" always;

    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

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

    # SPA routing - all routes go to index.html
    location / {
        try_files $uri $uri/ /index.html;
    }

    # API proxy (optional - if frontend and backend on same domain)
    location /api {
        proxy_pass http://backend:3000;
        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;
    }
}
```

### 3.4 Docker Compose - Development

**File:** `docker-compose.dev.yml`

```yaml
version: '3.9'

services:
  postgres:
    image: postgres:16-alpine
    container_name: erp-postgres-dev
    environment:
      POSTGRES_DB: erp_generic_dev
      POSTGRES_USER: erp_user
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-dev_password_change_me}
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
    volumes:
      - postgres_data_dev:/var/lib/postgresql/data
      - ./database/init-scripts:/docker-entrypoint-initdb.d:ro
    ports:
      - "5432:5432"
    networks:
      - erp-network
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U erp_user -d erp_generic_dev"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    container_name: erp-redis-dev
    command: redis-server --requirepass ${REDIS_PASSWORD:-dev_redis_pass} --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data_dev:/data
    ports:
      - "6379:6379"
    networks:
      - erp-network
    healthcheck:
      test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: builder  # Use builder stage for development (includes dev dependencies)
    container_name: erp-backend-dev
    environment:
      NODE_ENV: development
      DATABASE_URL: postgresql://erp_user:${POSTGRES_PASSWORD:-dev_password_change_me}@postgres:5432/erp_generic_dev?schema=public
      REDIS_URL: redis://default:${REDIS_PASSWORD:-dev_redis_pass}@redis:6379
      JWT_SECRET: ${JWT_SECRET:-dev_jwt_secret_change_me}
      JWT_EXPIRES_IN: 7d
      PORT: 3000
      LOG_LEVEL: debug
    volumes:
      - ./backend:/app
      - /app/node_modules
      - /app/dist
    ports:
      - "3000:3000"
      - "9229:9229"  # Debugger port
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - erp-network
    command: npm run start:dev
    restart: unless-stopped

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      target: builder
    container_name: erp-frontend-dev
    environment:
      VITE_API_URL: http://localhost:3000/api
      VITE_APP_ENV: development
    volumes:
      - ./frontend:/app
      - /app/node_modules
      - /app/dist
    ports:
      - "5173:5173"
    depends_on:
      - backend
    networks:
      - erp-network
    command: npm run dev -- --host
    restart: unless-stopped

volumes:
  postgres_data_dev:
    name: erp_postgres_data_dev
  redis_data_dev:
    name: erp_redis_data_dev

networks:
  erp-network:
    name: erp-network-dev
    driver: bridge
```

### 3.5 Docker Compose - Production

**File:** `docker-compose.prod.yml`

```yaml
version: '3.9'

services:
  postgres:
    image: postgres:16-alpine
    container_name: erp-postgres
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=en_US.UTF-8"
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./database/init-scripts:/docker-entrypoint-initdb.d:ro
      - /backups/postgres:/backups:rw
    # Do NOT expose ports publicly in production
    networks:
      - erp-network-internal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always
    deploy:
      resources:
        limits:
          cpus: '4'
          memory: 8G
        reservations:
          cpus: '2'
          memory: 4G
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:7-alpine
    container_name: erp-redis
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 2gb --maxmemory-policy allkeys-lru --appendonly yes
    volumes:
      - redis_data:/data
    networks:
      - erp-network-internal
    healthcheck:
      test: ["CMD", "redis-cli", "--raw", "incr", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    restart: always
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 3G
        reservations:
          cpus: '1'
          memory: 2G
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  backend:
    build:
      context: ./backend
      dockerfile: Dockerfile
      target: production
    image: ${DOCKER_REGISTRY}/erp-generic-backend:${VERSION:-latest}
    container_name: erp-backend
    environment:
      NODE_ENV: production
      DATABASE_URL: postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@postgres:5432/${POSTGRES_DB}?schema=public&connection_limit=20&pool_timeout=30
      REDIS_URL: redis://default:${REDIS_PASSWORD}@redis:6379
      JWT_SECRET: ${JWT_SECRET}
      JWT_EXPIRES_IN: ${JWT_EXPIRES_IN:-24h}
      JWT_REFRESH_EXPIRES_IN: ${JWT_REFRESH_EXPIRES_IN:-7d}
      PORT: 3000
      LOG_LEVEL: ${LOG_LEVEL:-info}
      ALLOWED_ORIGINS: ${ALLOWED_ORIGINS}
      SMTP_HOST: ${SMTP_HOST}
      SMTP_PORT: ${SMTP_PORT}
      SMTP_USER: ${SMTP_USER}
      SMTP_PASSWORD: ${SMTP_PASSWORD}
      S3_BUCKET: ${S3_BUCKET}
      S3_REGION: ${S3_REGION}
      AWS_ACCESS_KEY_ID: ${AWS_ACCESS_KEY_ID}
      AWS_SECRET_ACCESS_KEY: ${AWS_SECRET_ACCESS_KEY}
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - erp-network-internal
      - erp-network-external
    restart: always
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        order: start-first
      rollback_config:
        parallelism: 1
        delay: 5s
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G
    logging:
      driver: "json-file"
      options:
        max-size: "50m"
        max-file: "5"
    healthcheck:
      test: ["CMD", "node", "-e", "require('http').get('http://localhost:3000/health', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      target: production
    image: ${DOCKER_REGISTRY}/erp-generic-frontend:${VERSION:-latest}
    container_name: erp-frontend
    environment:
      VITE_API_URL: ${API_URL}
      VITE_APP_ENV: production
    depends_on:
      - backend
    networks:
      - erp-network-external
    restart: always
    deploy:
      replicas: 2
      update_config:
        parallelism: 1
        delay: 10s
      resources:
        limits:
          cpus: '1'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  nginx:
    image: nginx:1.25-alpine
    container_name: erp-nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./nginx/logs:/var/log/nginx:rw
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - frontend
      - backend
    networks:
      - erp-network-external
    restart: always
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
    logging:
      driver: "json-file"
      options:
        max-size: "20m"
        max-file: "5"

volumes:
  postgres_data:
    name: erp_postgres_data
    driver: local
    driver_opts:
      type: none
      o: bind
      device: /data/postgres
  redis_data:
    name: erp_redis_data
    driver: local

networks:
  erp-network-internal:
    name: erp-network-internal
    driver: bridge
    internal: true
  erp-network-external:
    name: erp-network-external
    driver: bridge
```

---

## 4. POSTGRESQL 16 SETUP

### 4.1 Initialization Script

**File:** `database/init-scripts/01-init-schemas.sql`

```sql
-- =====================================================
-- ERP GENERIC - PostgreSQL 16 Initialization
-- Creates 9 schemas + roles + extensions
-- =====================================================

-- Create extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";  -- For text search
CREATE EXTENSION IF NOT EXISTS "btree_gin"; -- For indexing
CREATE EXTENSION IF NOT EXISTS "pg_stat_statements"; -- For query monitoring

-- Create roles
CREATE ROLE erp_readonly;
CREATE ROLE erp_readwrite;
CREATE ROLE erp_admin;

-- Create 9 schemas
CREATE SCHEMA IF NOT EXISTS auth;
CREATE SCHEMA IF NOT EXISTS core;
CREATE SCHEMA IF NOT EXISTS financial;
CREATE SCHEMA IF NOT EXISTS inventory;
CREATE SCHEMA IF NOT EXISTS purchase;
CREATE SCHEMA IF NOT EXISTS sales;
CREATE SCHEMA IF NOT EXISTS analytics;
CREATE SCHEMA IF NOT EXISTS projects;
CREATE SCHEMA IF NOT EXISTS system;

-- Grant permissions to schemas
GRANT USAGE ON SCHEMA auth TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA core TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA financial TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA inventory TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA purchase TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA sales TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA analytics TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA projects TO erp_readonly, erp_readwrite, erp_admin;
GRANT USAGE ON SCHEMA system TO erp_readonly, erp_readwrite, erp_admin;

-- Grant SELECT to readonly
GRANT SELECT ON ALL TABLES IN SCHEMA auth TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA core TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA financial TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA inventory TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA purchase TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA sales TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA projects TO erp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA system TO erp_readonly;

-- Grant SELECT, INSERT, UPDATE, DELETE to readwrite
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA core TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA financial TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA inventory TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA purchase TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA sales TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA analytics TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA projects TO erp_readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA system TO erp_readwrite;

-- Grant ALL to admin
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA auth TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA core TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA financial TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA inventory TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA purchase TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA sales TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA analytics TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA projects TO erp_admin;
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA system TO erp_admin;

-- Default privileges for future tables
ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA financial GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA inventory GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA purchase GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA sales GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA projects GRANT SELECT ON TABLES TO erp_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA system GRANT SELECT ON TABLES TO erp_readonly;

ALTER DEFAULT PRIVILEGES IN SCHEMA auth GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA core GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA financial GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA inventory GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA purchase GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA sales GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA projects GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA system GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO erp_readwrite;

-- Enable Row Level Security (RLS) on all schemas
ALTER SCHEMA auth ENABLE;
ALTER SCHEMA core ENABLE;
ALTER SCHEMA financial ENABLE;
ALTER SCHEMA inventory ENABLE;
ALTER SCHEMA purchase ENABLE;
ALTER SCHEMA sales ENABLE;
ALTER SCHEMA analytics ENABLE;
ALTER SCHEMA projects ENABLE;
ALTER SCHEMA system ENABLE;

-- Create audit function
CREATE OR REPLACE FUNCTION system.audit_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    IF (TG_OP = 'INSERT') THEN
        NEW.created_at = CURRENT_TIMESTAMP;
        NEW.created_by = COALESCE(NEW.created_by, current_setting('app.current_user_id', TRUE)::UUID);
    ELSIF (TG_OP = 'UPDATE') THEN
        NEW.updated_at = CURRENT_TIMESTAMP;
        NEW.updated_by = current_setting('app.current_user_id', TRUE)::UUID;
        NEW.created_at = OLD.created_at;
        NEW.created_by = OLD.created_by;
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create tenant context function
CREATE OR REPLACE FUNCTION auth.get_current_tenant_id()
RETURNS UUID AS $$
BEGIN
    RETURN NULLIF(current_setting('app.current_tenant_id', TRUE), '')::UUID;
END;
$$ LANGUAGE plpgsql STABLE;

COMMENT ON FUNCTION auth.get_current_tenant_id() IS 'Returns the current tenant_id from session variable';

-- Success message
DO $$
BEGIN
    RAISE NOTICE 'ERP Generic PostgreSQL initialization completed successfully!';
    RAISE NOTICE 'Created 9 schemas: auth, core, financial, inventory, purchase, sales, analytics, projects, system';
    RAISE NOTICE 'Created 3 roles: erp_readonly, erp_readwrite, erp_admin';
    RAISE NOTICE 'Installed extensions: uuid-ossp, pgcrypto, pg_trgm, btree_gin, pg_stat_statements';
END $$;
```

### 4.2 PostgreSQL Configuration

**File:** `database/postgresql.conf` (Production optimizations)

```ini
# Connection Settings
max_connections = 200
superuser_reserved_connections = 3

# Memory Settings
shared_buffers = 4GB                    # 25% of RAM
effective_cache_size = 12GB             # 75% of RAM
maintenance_work_mem = 1GB
work_mem = 20MB                         # max_connections * work_mem < RAM
temp_buffers = 16MB

# Checkpoint Settings
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1                  # For SSD
effective_io_concurrency = 200          # For SSD

# WAL Settings (for PITR - Point In Time Recovery)
wal_level = replica
archive_mode = on
archive_command = 'test ! -f /backups/wal/%f && cp %p /backups/wal/%f'
max_wal_senders = 3
wal_keep_size = 1GB

# Query Planning
default_statistics_target = 100
random_page_cost = 1.1

# Logging
log_destination = 'stderr'
logging_collector = on
log_directory = '/var/log/postgresql'
log_filename = 'postgresql-%Y-%m-%d_%H%M%S.log'
log_rotation_age = 1d
log_rotation_size = 100MB
log_min_duration_statement = 1000       # Log queries slower than 1s
log_line_prefix = '%m [%p] %q%u@%d '
log_checkpoints = on
log_connections = on
log_disconnections = on
log_duration = off
log_lock_waits = on
log_statement = 'ddl'                   # Log DDL statements only
log_temp_files = 0

# Performance Monitoring
shared_preload_libraries = 'pg_stat_statements'
pg_stat_statements.track = all
pg_stat_statements.max = 10000

# Locale and Formatting
datestyle = 'iso, mdy'
timezone = 'UTC'
lc_messages = 'en_US.utf8'
lc_monetary = 'en_US.utf8'
lc_numeric = 'en_US.utf8'
lc_time = 'en_US.utf8'
default_text_search_config = 'pg_catalog.english'
```

### 4.3 Database Migrations

```bash
# Run Prisma migrations (creates tables, indexes, constraints)
docker-compose exec backend npx prisma migrate deploy

# Verify migration status
docker-compose exec backend npx prisma migrate status

# Generate Prisma Client
docker-compose exec backend npx prisma generate

# Seed initial data
docker-compose exec backend npm run seed:initial
```

---

## 5. REDIS SETUP

### 5.1 Redis Configuration

**File:** `redis/redis.conf`

```conf
# Network
bind 0.0.0.0
protected-mode yes
port 6379
tcp-backlog 511
timeout 0
tcp-keepalive 300

# Security
requirepass ${REDIS_PASSWORD}
# Disable dangerous commands
rename-command FLUSHDB ""
rename-command FLUSHALL ""
rename-command KEYS ""
rename-command CONFIG ""

# Memory Management
maxmemory 2gb
maxmemory-policy allkeys-lru
maxmemory-samples 5

# Persistence (AOF for durability)
appendonly yes
appendfilename "appendonly.aof"
appendfsync everysec
no-appendfsync-on-rewrite no
auto-aof-rewrite-percentage 100
auto-aof-rewrite-min-size 64mb

# RDB Snapshots (backup)
save 900 1
save 300 10
save 60 10000
stop-writes-on-bgsave-error yes
rdbcompression yes
rdbchecksum yes
dbfilename dump.rdb

# Logging
loglevel notice
logfile "/var/log/redis/redis-server.log"

# Slow log
slowlog-log-slower-than 10000
slowlog-max-len 128

# Performance
databases 16
```

### 5.2 Redis Usage in ERP

**Session Storage:**
```typescript
// backend/src/common/redis/redis.service.ts
@Injectable()
export class RedisService {
  private client: Redis;

  constructor() {
    this.client = new Redis({
      host: process.env.REDIS_HOST || 'redis',
      port: parseInt(process.env.REDIS_PORT) || 6379,
      password: process.env.REDIS_PASSWORD,
      db: 0, // Sessions DB
      retryStrategy: (times) => Math.min(times * 50, 2000),
    });
  }

  // Session management
  async setSession(sessionId: string, data: any, ttl: number = 86400) {
    await this.client.setex(
      `session:${sessionId}`,
      ttl,
      JSON.stringify(data)
    );
  }

  async getSession(sessionId: string): Promise<any | null> {
    const data = await this.client.get(`session:${sessionId}`);
    return data ? JSON.parse(data) : null;
  }

  async deleteSession(sessionId: string): Promise<void> {
    await this.client.del(`session:${sessionId}`);
  }

  // Cache management
  async cacheSet(key: string, value: any, ttl: number = 3600) {
    await this.client.setex(
      `cache:${key}`,
      ttl,
      JSON.stringify(value)
    );
  }

  async cacheGet(key: string): Promise<any | null> {
    const data = await this.client.get(`cache:${key}`);
    return data ? JSON.parse(data) : null;
  }

  async cacheInvalidate(pattern: string): Promise<void> {
    const keys = await this.client.keys(`cache:${pattern}*`);
    if (keys.length > 0) {
      await this.client.del(...keys);
    }
  }
}
```

---

## 6. ENVIRONMENT VARIABLES

### 6.1 .env.example

**File:** `.env.example`

```bash
# =====================================================
# ERP GENERIC - Environment Variables
# =====================================================

# Application
NODE_ENV=production
APP_NAME=ERP Generic
APP_URL=https://erp-generic.com
PORT=3000
VERSION=1.0.0

# Database (PostgreSQL 16)
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=erp_generic
POSTGRES_USER=erp_user
POSTGRES_PASSWORD=CHANGE_ME_STRONG_PASSWORD_32_CHARS
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}?schema=public&connection_limit=20&pool_timeout=30

# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=CHANGE_ME_REDIS_PASSWORD
REDIS_URL=redis://default:${REDIS_PASSWORD}@${REDIS_HOST}:${REDIS_PORT}

# JWT Authentication
JWT_SECRET=CHANGE_ME_JWT_SECRET_64_CHARS_RANDOM_STRING_SECURE
JWT_EXPIRES_IN=24h
JWT_REFRESH_SECRET=CHANGE_ME_REFRESH_SECRET_64_CHARS
JWT_REFRESH_EXPIRES_IN=7d

# CORS
ALLOWED_ORIGINS=https://erp-generic.com,https://www.erp-generic.com,https://*.erp-generic.com

# Email (SMTP)
SMTP_HOST=smtp.sendgrid.net
SMTP_PORT=587
SMTP_SECURE=false
SMTP_USER=apikey
SMTP_PASSWORD=SENDGRID_API_KEY
SMTP_FROM_EMAIL=noreply@erp-generic.com
SMTP_FROM_NAME=ERP Generic

# AWS S3 (File Storage)
S3_BUCKET=erp-generic-prod
S3_REGION=us-east-1
AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# Logging
LOG_LEVEL=info
LOG_FORMAT=json
SENTRY_DSN=https://your-sentry-dsn@sentry.io/project-id

# Rate Limiting
RATE_LIMIT_TTL=60
RATE_LIMIT_MAX=100

# Docker Registry
DOCKER_REGISTRY=ghcr.io/your-org

# Monitoring
PROMETHEUS_PORT=9090
GRAFANA_PORT=3001

# Multi-Tenancy
DEFAULT_TENANT_SCHEMA=tenant_default
ENABLE_TENANT_ISOLATION=true
```

### 6.2 Environment-Specific Variables

**Development (`.env.development`):**
```bash
NODE_ENV=development
LOG_LEVEL=debug
DATABASE_URL=postgresql://erp_user:dev_pass@localhost:5432/erp_generic_dev
REDIS_URL=redis://default:dev_redis@localhost:6379
JWT_SECRET=dev_jwt_secret_not_secure
ALLOWED_ORIGINS=http://localhost:5173,http://localhost:3000
```

**QA (`.env.qa`):**
```bash
NODE_ENV=qa
LOG_LEVEL=info
DATABASE_URL=postgresql://erp_user:qa_pass@postgres-qa:5432/erp_generic_qa
REDIS_URL=redis://default:qa_redis@redis-qa:6379
JWT_SECRET=qa_jwt_secret_secure_32_chars
ALLOWED_ORIGINS=https://qa.erp-generic.local
```

**Staging (`.env.staging`):**
```bash
NODE_ENV=staging
LOG_LEVEL=info
DATABASE_URL=postgresql://erp_user:staging_pass@postgres-staging:5432/erp_generic_staging
REDIS_URL=redis://default:staging_redis@redis-staging:6379
JWT_SECRET=staging_jwt_secret_secure_64_chars
ALLOWED_ORIGINS=https://staging.erp-generic.com
S3_BUCKET=erp-generic-staging
SENTRY_DSN=https://staging-sentry-dsn@sentry.io/project-id
```

**Production (`.env.production`):**
```bash
NODE_ENV=production
LOG_LEVEL=warn
DATABASE_URL=postgresql://erp_user:PROD_PASSWORD@postgres-prod:5432/erp_generic
REDIS_URL=redis://default:PROD_REDIS@redis-prod:6379
JWT_SECRET=PROD_JWT_SECRET_SECURE_64_CHARS_RANDOM
ALLOWED_ORIGINS=https://erp-generic.com,https://www.erp-generic.com
S3_BUCKET=erp-generic-prod
SENTRY_DSN=https://prod-sentry-dsn@sentry.io/project-id
```

### 6.3 Secret Management

**Using AWS Secrets Manager:**
```bash
# Fetch secrets from AWS Secrets Manager
aws secretsmanager get-secret-value --secret-id erp-generic/prod/database --query SecretString --output text > .env.secrets

# Or use in docker-compose
services:
  backend:
    environment:
      DATABASE_PASSWORD: "{{secrets:erp-generic/prod/database:password}}"
```

**Using HashiCorp Vault:**
```bash
# Fetch secrets from Vault
vault kv get -field=password secret/erp-generic/prod/database
```

---

## 7. MULTI-ENVIRONMENT STRATEGY

### 7.1 Environment Pipeline

```
Development → QA → Staging → Production
    ↓          ↓      ↓         ↓
  Manual     Auto   Manual   Manual
  Deploy    Deploy  Deploy   Deploy
```

### 7.2 Deployment Matrix

| Characteristic | Development | QA | Staging | Production |
|----------------|-------------|-----|---------|------------|
| **Trigger** | Manual | Auto (push to develop) | Manual (approved PR) | Manual (release tag) |
| **Database** | Local PostgreSQL | Anonymized prod | Prod clone | Production |
| **Redis** | Local Redis | Dedicated QA | Dedicated Staging | Production cluster |
| **Replicas** | 1 | 1 | 2 | 3+ |
| **Resources** | Minimal | Low | Medium | High |
| **Monitoring** | Basic logs | Prometheus + Grafana | Full stack | Full stack + alerts |
| **Backups** | None | Daily | Hourly | Every 4 hours |
| **SSL** | Self-signed | Let's Encrypt | Let's Encrypt | Commercial cert |
| **Domain** | localhost | qa.local | staging.com | erp-generic.com |

### 7.3 Promotion Criteria

**Development → QA:**
- [ ] All unit tests passing (>80% coverage)
- [ ] Code review approved
- [ ] No TypeScript errors
- [ ] Linter passing

**QA → Staging:**
- [ ] All integration tests passing
- [ ] E2E tests passing (critical flows)
- [ ] Manual QA sign-off
- [ ] Performance tests passing
- [ ] Security scan passing (no critical vulnerabilities)

**Staging → Production:**
- [ ] Staging validation complete (48 hours soak test)
- [ ] Load testing passed (>1000 req/s)
- [ ] Backup verified
- [ ] Rollback plan documented
- [ ] Change approval from Product Owner + CTO
- [ ] Communication sent to stakeholders

---

## 8. DEPLOYMENT STEPS

### 8.1 First-Time Deployment (Fresh Install)

```bash
# Step 1: Clone repository
git clone https://github.com/your-org/erp-generic.git
cd erp-generic

# Step 2: Configure environment
cp .env.example .env.production
nano .env.production  # Edit with real values

# Step 3: Generate secrets
JWT_SECRET=$(openssl rand -hex 32)
POSTGRES_PASSWORD=$(openssl rand -base64 32)
REDIS_PASSWORD=$(openssl rand -base64 32)

# Update .env.production with generated secrets

# Step 4: Start infrastructure services first
docker-compose -f docker-compose.prod.yml up -d postgres redis

# Wait for databases to be healthy
docker-compose -f docker-compose.prod.yml ps

# Step 5: Run database initialization
docker-compose -f docker-compose.prod.yml exec postgres psql -U erp_user -d erp_generic -f /docker-entrypoint-initdb.d/01-init-schemas.sql

# Step 6: Run Prisma migrations
docker-compose -f docker-compose.prod.yml run --rm backend npx prisma migrate deploy

# Step 7: Seed initial data
docker-compose -f docker-compose.prod.yml run --rm backend npm run seed:initial

# Step 8: Start application services
docker-compose -f docker-compose.prod.yml up -d backend frontend nginx

# Step 9: Verify health
./scripts/health-check.sh

# Step 10: Check logs
docker-compose -f docker-compose.prod.yml logs -f backend

# Step 11: Access application
curl -I https://erp-generic.com
# Expected: HTTP/1.1 200 OK
```

**Total time:** 15-20 minutes

### 8.2 Update Deployment (Rolling Update)

```bash
# Step 1: Backup database (safety first!)
./scripts/backup-postgres.sh

# Step 2: Pull latest changes
git fetch origin
git checkout v1.2.0  # Or specific release tag

# Step 3: Update environment variables (if needed)
diff .env.example .env.production
# Add any new variables

# Step 4: Build new images
docker-compose -f docker-compose.prod.yml build backend frontend

# Step 5: Tag images with version
docker tag erp-generic-backend:latest erp-generic-backend:v1.2.0
docker tag erp-generic-frontend:latest erp-generic-frontend:v1.2.0

# Step 6: Run database migrations (zero-downtime)
docker-compose -f docker-compose.prod.yml run --rm backend npx prisma migrate deploy

# Step 7: Rolling update backend (one replica at a time)
docker-compose -f docker-compose.prod.yml up -d --no-deps --scale backend=4 backend
# Wait 30s for health checks
docker-compose -f docker-compose.prod.yml scale backend=3

# Step 8: Update frontend
docker-compose -f docker-compose.prod.yml up -d --no-deps frontend

# Step 9: Verify health
./scripts/health-check.sh

# Step 10: Run smoke tests
npm run test:smoke:production

# Step 11: Monitor for 15 minutes
docker-compose -f docker-compose.prod.yml logs -f backend | grep ERROR
```

**Total time:** 5-10 minutes

### 8.3 Hotfix Deployment (Emergency)

```bash
# Step 1: Create hotfix branch
git checkout -b hotfix/critical-security-fix

# Step 2: Implement fix + tests
# ... make changes ...
npm run test

# Step 3: Fast-track deployment (skip QA for critical issues)
git commit -m "hotfix: Fix critical security vulnerability"
git push origin hotfix/critical-security-fix

# Step 4: Deploy directly to production (with approval)
./scripts/deploy-hotfix.sh hotfix/critical-security-fix

# Step 5: Monitor closely
tail -f /var/log/erp-generic/backend.log

# Step 6: Communicate to stakeholders
# Send email notification about hotfix deployment
```

**Total time:** 5-15 minutes (depending on complexity)

---

## 9. ZERO-DOWNTIME DEPLOYMENT

### 9.1 Blue-Green Deployment Strategy

```
┌─────────────────────────────────────────────────────┐
│              Load Balancer (Nginx)                  │
│         (Routes traffic to active env)              │
└─────┬──────────────────────────────────────┬────────┘
      │                                      │
      │ 100% traffic                         │ 0% traffic
      ↓                                      ↓
┌──────────────┐                      ┌──────────────┐
│ Blue (v1.1)  │                      │ Green (v1.2) │
│   ACTIVE     │                      │   STANDBY    │
│              │                      │              │
│  Backend x3  │                      │  Backend x3  │
│  Frontend    │                      │  Frontend    │
│  Database    │                      │  Database    │
└──────────────┘                      └──────────────┘

After testing Green:
      │ 0% traffic                           │ 100% traffic
      ↓                                      ↓
┌──────────────┐                      ┌──────────────┐
│ Blue (v1.1)  │                      │ Green (v1.2) │
│   STANDBY    │  ← Rollback          │   ACTIVE     │
└──────────────┘                      └──────────────┘
```

### 9.2 Implementation

**File:** `scripts/deploy-blue-green.sh`

```bash
#!/bin/bash
set -euo pipefail

CURRENT_ENV=${1:-blue}
NEW_VERSION=${2:-latest}

echo "===== Blue-Green Deployment ====="
echo "Current environment: $CURRENT_ENV"
echo "New version: $NEW_VERSION"

# Determine new environment
if [ "$CURRENT_ENV" = "blue" ]; then
    NEW_ENV="green"
else
    NEW_ENV="blue"
fi

echo "Deploying to: $NEW_ENV"

# Step 1: Deploy to standby environment
echo "1. Deploying to $NEW_ENV environment..."
docker-compose -f docker-compose.$NEW_ENV.yml up -d --build

# Step 2: Wait for health checks
echo "2. Waiting for health checks..."
sleep 30

for i in {1..10}; do
    if ./scripts/health-check.sh $NEW_ENV; then
        echo "Health check passed!"
        break
    fi

    if [ $i -eq 10 ]; then
        echo "Health check failed after 10 attempts. Aborting deployment."
        exit 1
    fi

    echo "Health check failed. Retrying in 10s... ($i/10)"
    sleep 10
done

# Step 3: Run smoke tests
echo "3. Running smoke tests on $NEW_ENV..."
npm run test:smoke -- --env=$NEW_ENV

if [ $? -ne 0 ]; then
    echo "Smoke tests failed. Aborting deployment."
    exit 1
fi

# Step 4: Switch traffic to new environment
echo "4. Switching traffic to $NEW_ENV..."
# Update nginx config to point to new environment
sed -i "s/upstream backend_$CURRENT_ENV/upstream backend_$NEW_ENV/g" /etc/nginx/nginx.conf
nginx -s reload

echo "Traffic switched to $NEW_ENV"

# Step 5: Monitor for 5 minutes
echo "5. Monitoring $NEW_ENV for 5 minutes..."
sleep 300

ERROR_COUNT=$(docker-compose -f docker-compose.$NEW_ENV.yml logs backend | grep -c ERROR || true)

if [ $ERROR_COUNT -gt 10 ]; then
    echo "Too many errors detected ($ERROR_COUNT). Rolling back..."
    ./scripts/rollback.sh $CURRENT_ENV
    exit 1
fi

# Step 6: Shutdown old environment
echo "6. Shutting down old environment $CURRENT_ENV..."
docker-compose -f docker-compose.$CURRENT_ENV.yml down

echo "===== Deployment Complete ====="
echo "Active environment: $NEW_ENV"
echo "Version: $NEW_VERSION"
```

### 9.3 Health Check Endpoint

```typescript
// backend/src/health/health.controller.ts
import { Controller, Get } from '@nestjs/common';
import { HealthCheck, HealthCheckService, PrismaHealthIndicator, MemoryHealthIndicator } from '@nestjs/terminus';
import { RedisHealthIndicator } from './redis.health';

@Controller('health')
export class HealthController {
  constructor(
    private health: HealthCheckService,
    private db: PrismaHealthIndicator,
    private redis: RedisHealthIndicator,
    private memory: MemoryHealthIndicator,
  ) {}

  @Get()
  @HealthCheck()
  check() {
    return this.health.check([
      // Database check
      () => this.db.pingCheck('database', { timeout: 3000 }),

      // Redis check
      () => this.redis.isHealthy('redis'),

      // Memory check (heap should not exceed 150MB)
      () => this.memory.checkHeap('memory_heap', 150 * 1024 * 1024),

      // Memory check (RSS should not exceed 300MB)
      () => this.memory.checkRSS('memory_rss', 300 * 1024 * 1024),
    ]);
  }

  @Get('live')
  liveness() {
    // Simple liveness probe for Kubernetes
    return { status: 'ok', timestamp: new Date().toISOString() };
  }

  @Get('ready')
  @HealthCheck()
  readiness() {
    // Readiness probe - checks all dependencies
    return this.health.check([
      () => this.db.pingCheck('database'),
      () => this.redis.isHealthy('redis'),
    ]);
  }
}
```

---

## 10. ROLLBACK PROCEDURES

### 10.1 Automatic Rollback

**Trigger conditions:**
- Health checks failing for >2 minutes
- Error rate >5% for >5 minutes
- Database migration failure
- Critical exception thrown

**File:** `scripts/rollback.sh`

```bash
#!/bin/bash
set -euo pipefail

ROLLBACK_TO_VERSION=${1:-previous}

echo "===== EMERGENCY ROLLBACK ====="
echo "Rolling back to: $ROLLBACK_TO_VERSION"

# Step 1: Identify current and previous versions
CURRENT_VERSION=$(docker inspect erp-backend --format='{{.Config.Labels.version}}')
echo "Current version: $CURRENT_VERSION"

if [ "$ROLLBACK_TO_VERSION" = "previous" ]; then
    ROLLBACK_TO_VERSION=$(git describe --tags --abbrev=0 HEAD^)
fi

echo "Rollback to version: $ROLLBACK_TO_VERSION"

# Step 2: Stop current containers
echo "Stopping current containers..."
docker-compose -f docker-compose.prod.yml stop backend frontend

# Step 3: Restore database backup (if needed)
read -p "Restore database backup? (y/N) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    echo "Restoring database..."
    LATEST_BACKUP=$(ls -t /backups/postgres/full_*.dump | head -1)
    ./scripts/restore-postgres.sh --backup=$LATEST_BACKUP --no-prompt
fi

# Step 4: Checkout previous version
echo "Checking out version $ROLLBACK_TO_VERSION..."
git fetch --tags
git checkout tags/$ROLLBACK_TO_VERSION

# Step 5: Start services with previous version
echo "Starting services with version $ROLLBACK_TO_VERSION..."
docker-compose -f docker-compose.prod.yml up -d backend frontend

# Step 6: Verify health
echo "Waiting for services to start..."
sleep 30

./scripts/health-check.sh

if [ $? -eq 0 ]; then
    echo "===== ROLLBACK SUCCESSFUL ====="
    echo "Rolled back from $CURRENT_VERSION to $ROLLBACK_TO_VERSION"

    # Notify team
    curl -X POST https://hooks.slack.com/services/YOUR/SLACK/WEBHOOK \
        -H 'Content-Type: application/json' \
        -d "{\"text\": \"🚨 EMERGENCY ROLLBACK: $CURRENT_VERSION → $ROLLBACK_TO_VERSION\"}"
else
    echo "===== ROLLBACK FAILED ====="
    echo "Manual intervention required!"
    exit 1
fi
```

### 10.2 Manual Rollback Steps

```bash
# 1. Identify issue and decide to rollback
# Check logs, monitoring dashboards, alerts

# 2. Execute rollback script
./scripts/rollback.sh v1.1.0

# 3. If automated rollback fails, manual steps:

# Stop services
docker-compose -f docker-compose.prod.yml stop

# Restore database from backup
pg_restore -h localhost -U erp_user -d erp_generic /backups/postgres/full_20251124_020000.dump

# Checkout previous stable version
git checkout tags/v1.1.0

# Start services
docker-compose -f docker-compose.prod.yml up -d

# 4. Verify system is stable
./scripts/health-check.sh
npm run test:smoke

# 5. Communicate to stakeholders
# Post-mortem within 48 hours
```

### 10.3 Database Rollback

**Forward-only migrations (recommended):**
```bash
# Never use "prisma migrate reset" in production!

# Instead, create a new migration to undo changes
npx prisma migrate dev --name revert_feature_x

# Example: If you added a column, create migration to drop it
# migrations/20251124_revert_feature_x/migration.sql:
ALTER TABLE core.products DROP COLUMN IF EXISTS new_field;
```

**Emergency database rollback (last resort):**
```bash
# 1. Stop application
docker-compose stop backend

# 2. Create safety backup
pg_dump -h localhost -U erp_user -Fc erp_generic > /backups/pre-rollback-$(date +%Y%m%d_%H%M%S).dump

# 3. Restore from backup (point-in-time before bad deployment)
pg_restore -h localhost -U erp_user -d erp_generic -c /backups/postgres/full_20251124_020000.dump

# 4. Verify data integrity
psql -h localhost -U erp_user -d erp_generic -c "SELECT COUNT(*) FROM auth.users;"

# 5. Restart application
docker-compose start backend
```

---

## 11. TROUBLESHOOTING

### 11.1 Common Issues

**Issue: Backend fails to start**

```bash
# Check logs
docker-compose logs backend

# Common causes:
# 1. Database connection failed
docker-compose exec postgres pg_isready
# Fix: Verify DATABASE_URL, check PostgreSQL is running

# 2. Redis connection failed
docker-compose exec redis redis-cli ping
# Fix: Verify REDIS_URL, check Redis is running

# 3. Missing environment variables
docker-compose exec backend env | grep DATABASE_URL
# Fix: Update .env file

# 4. Port already in use
sudo lsof -i :3000
# Fix: Stop conflicting process or change PORT
```

**Issue: Database migrations fail**

```bash
# Check migration status
npx prisma migrate status

# View pending migrations
npx prisma migrate resolve

# Force migration (CAUTION: only in dev)
npx prisma migrate resolve --applied "20251124_migration_name"

# Production fix: Create compensating migration
npx prisma migrate dev --name fix_migration_issue
```

**Issue: High memory usage**

```bash
# Check container stats
docker stats

# If PostgreSQL using too much memory:
# Reduce shared_buffers in postgresql.conf
# Restart: docker-compose restart postgres

# If Node.js using too much memory:
# Add heap limit: NODE_OPTIONS="--max-old-space-size=2048"
# Restart: docker-compose restart backend
```

**Issue: Slow API responses**

```bash
# Check database query performance
docker-compose exec postgres psql -U erp_user -d erp_generic

# View slow queries
SELECT query, mean_exec_time, calls
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;

# View active connections
SELECT count(*) FROM pg_stat_activity;

# View locks
SELECT * FROM pg_locks WHERE NOT granted;

# Fix: Add indexes, optimize queries, increase resources
```

### 11.2 Disaster Recovery Checklist

**Database Corruption:**
- [ ] Stop application immediately
- [ ] Identify corruption scope (single table vs full database)
- [ ] Restore from latest backup
- [ ] Replay WAL logs for point-in-time recovery
- [ ] Verify data integrity
- [ ] Restart application

**Complete System Failure:**
- [ ] Assess damage (hardware failure, network outage, etc.)
- [ ] Provision new infrastructure (if needed)
- [ ] Restore database from backup (cloud or offsite)
- [ ] Deploy latest stable version
- [ ] Restore Redis data (if needed)
- [ ] Verify system health
- [ ] Resume operations
- [ ] Post-mortem analysis

**Security Breach:**
- [ ] Isolate compromised system immediately
- [ ] Rotate all credentials (database, API keys, JWT secrets)
- [ ] Audit access logs
- [ ] Patch vulnerability
- [ ] Deploy patched version
- [ ] Notify affected users (if data leaked)
- [ ] Conduct full security audit

---

## 12. REFERENCES

**Internal Documentation:**
- [README.md](./README.md) - DevOps overview
- [MONITORING-OBSERVABILITY.md](./MONITORING-OBSERVABILITY.md) - Monitoring setup
- [BACKUP-RECOVERY.md](./BACKUP-RECOVERY.md) - Backup procedures
- [SECURITY-HARDENING.md](./SECURITY-HARDENING.md) - Security hardening
- [CI-CD-PIPELINE.md](./CI-CD-PIPELINE.md) - CI/CD pipeline
- [Database Schemas](../02-modelado/database-design/schemas/) - Database DDL
- [ADR-001: Stack Tecnológico](../adr/ADR-001-stack-tecnologico.md)
- [ADR-003: Multi-Tenancy](../adr/ADR-003-multi-tenancy.md)

**External Resources:**
- [Docker Documentation](https://docs.docker.com/)
- [Docker Compose Documentation](https://docs.docker.com/compose/)
- [PostgreSQL 16 Documentation](https://www.postgresql.org/docs/16/)
- [Redis Documentation](https://redis.io/documentation)
- [NestJS Documentation](https://docs.nestjs.com/)
- [Prisma Documentation](https://www.prisma.io/docs/)
- [12-Factor App](https://12factor.net/)
- [Blue-Green Deployment Pattern](https://martinfowler.com/bliki/BlueGreenDeployment.html)

---

## APPENDIX A: Quick Reference Commands

```bash
# Health check
./scripts/health-check.sh

# View logs
docker-compose logs -f backend
docker-compose logs -f frontend
docker-compose logs -f postgres
docker-compose logs -f redis

# Database console
docker-compose exec postgres psql -U erp_user -d erp_generic

# Redis console
docker-compose exec redis redis-cli -a $REDIS_PASSWORD

# Run migrations
docker-compose exec backend npx prisma migrate deploy

# Backup database
./scripts/backup-postgres.sh

# Restore database
./scripts/restore-postgres.sh --backup=full_20251124_020000.dump

# Rollback deployment
./scripts/rollback.sh v1.1.0

# View container stats
docker stats

# Restart service
docker-compose restart backend

# Scale service
docker-compose up -d --scale backend=5
```

---

**Documento:** DEPLOYMENT-GUIDE.md
**Versión:** 1.0
**Total Páginas:** ~18
**Última Actualización:** 2025-11-24
**Próxima Revisión:** Mensual
