# CI/CD PIPELINE - ERP Generic

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

---

## TABLE OF CONTENTS

1. [Overview](#1-overview)
2. [CI/CD Architecture](#2-cicd-architecture)
3. [GitHub Actions Workflows](#3-github-actions-workflows)
4. [Automated Testing Integration](#4-automated-testing-integration)
5. [Code Quality Gates](#5-code-quality-gates)
6. [Security Scanning](#6-security-scanning)
7. [Docker Build & Push](#7-docker-build--push)
8. [Deployment Automation](#8-deployment-automation)
9. [Rollback Automation](#9-rollback-automation)
10. [Notifications](#10-notifications)

---

## 1. OVERVIEW

### 1.1 CI/CD Pipeline Architecture

```
┌─────────────────────────────────────────────────────────────┐
│                   Developer Workflow                        │
│  1. git push origin feature/new-feature                     │
│  2. Create Pull Request                                     │
└────────────┬────────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────┐
│              CI Pipeline (GitHub Actions)                   │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐           │
│  │  Lint  │→ │  Build │→ │  Test  │→ │Security│           │
│  │        │  │        │  │ (1.5k) │  │  Scan  │           │
│  └────────┘  └────────┘  └────────┘  └────────┘           │
│       ↓          ↓           ↓           ↓                  │
│  ┌─────────────────────────────────────────────┐           │
│  │  Quality Gate (SonarQube)                   │           │
│  │  - Coverage >80%                            │           │
│  │  - No critical bugs                         │           │
│  └─────────────────────────────────────────────┘           │
└────────────┬────────────────────────────────────────────────┘
             │ (If all checks pass)
             ▼
┌─────────────────────────────────────────────────────────────┐
│            CD Pipeline (Deployment)                         │
│                                                             │
│  develop branch → QA (Auto)                                │
│  main branch    → Staging (Manual approval)                │
│  release tags   → Production (Manual approval)             │
│                                                             │
│  ┌────────┐  ┌────────┐  ┌────────┐  ┌────────┐          │
│  │ Build  │→ │  Push  │→ │ Deploy │→ │ Verify │          │
│  │ Docker │  │   to   │  │   to   │  │ Health │          │
│  │ Image  │  │Registry│  │ Server │  │ Checks │          │
│  └────────┘  └────────┘  └────────┘  └────────┘          │
└────────────┬────────────────────────────────────────────────┘
             │
             ▼
┌─────────────────────────────────────────────────────────────┐
│              Post-Deployment                                │
│  - Health checks                                            │
│  - Smoke tests                                              │
│  - Notifications (Slack/Discord)                           │
│  - Rollback if failure                                      │
└─────────────────────────────────────────────────────────────┘
```

### 1.2 Pipeline Stages

| Stage | Duration | Trigger | Blocking |
|-------|----------|---------|----------|
| **Lint** | 30s | Every push | Yes |
| **Build** | 2-3 min | Every push | Yes |
| **Unit Tests** | 1-2 min | Every push | Yes |
| **Integration Tests** | 5-10 min | Every push | Yes |
| **E2E Tests** | 15-30 min | Push to main/develop | No (parallel) |
| **Security Scan** | 2-5 min | Every push | Yes (critical only) |
| **Quality Gate** | 1 min | Every push | Yes |
| **Docker Build** | 5-10 min | Merge to main/develop | No |
| **Deploy QA** | 3-5 min | Merge to develop | No |
| **Deploy Production** | 10-15 min | Release tag + approval | No |

**Total CI time:** ~15-25 minutes (blocking)
**Total CD time:** 3-15 minutes (per environment)

---

## 2. CI/CD ARCHITECTURE

### 2.1 Branching Strategy (GitFlow)

```
main (production)
  │
  ├── release/v1.0.0 (staging)
  │   │
  │   └── hotfix/critical-bug
  │
  └── develop (QA)
      │
      ├── feature/user-authentication
      ├── feature/sales-orders
      └── bugfix/fix-inventory-calc
```

**Branch Protection Rules:**

```yaml
# main branch
required_reviews: 2
require_code_owner_review: true
dismiss_stale_reviews: true
require_linear_history: true
required_status_checks:
  - ci/lint
  - ci/build
  - ci/test
  - ci/security
  - ci/quality-gate

# develop branch
required_reviews: 1
require_code_owner_review: false
required_status_checks:
  - ci/lint
  - ci/test
```

### 2.2 Deployment Environments

| Environment | Branch | Trigger | Approval Required |
|-------------|--------|---------|-------------------|
| **Development** | feature/* | Manual | No |
| **QA** | develop | Automatic | No |
| **Staging** | main | Manual | Yes (Tech Lead) |
| **Production** | tags/v* | Manual | Yes (CTO + PO) |

---

## 3. GITHUB ACTIONS WORKFLOWS

### 3.1 CI Workflow (Main Pipeline)

**File:** `.github/workflows/ci.yml`

```yaml
name: CI Pipeline

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main, develop]

env:
  NODE_VERSION: '20'
  PNPM_VERSION: '8'

jobs:
  lint:
    name: Lint Code
    runs-on: ubuntu-latest
    timeout-minutes: 5
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run ESLint
        run: npm run lint

      - name: Run Prettier check
        run: npm run format:check

  build:
    name: Build Application
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: lint
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Build backend
        run: npm run build --workspace=backend

      - name: Build frontend
        run: npm run build --workspace=frontend

      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build-artifacts
          path: |
            backend/dist
            frontend/dist
          retention-days: 7

  test-unit:
    name: Unit Tests
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: build
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests (Backend)
        run: npm run test:unit --workspace=backend -- --coverage

      - name: Run unit tests (Frontend)
        run: npm run test:unit --workspace=frontend -- --coverage

      - name: Upload coverage to Codecov
        uses: codecov/codecov-action@v3
        with:
          files: ./backend/coverage/coverage-final.json,./frontend/coverage/coverage-final.json
          flags: unit-tests
          name: unit-tests-coverage

  test-integration:
    name: Integration Tests
    runs-on: ubuntu-latest
    timeout-minutes: 20
    needs: build
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: test_db
          POSTGRES_USER: test_user
          POSTGRES_PASSWORD: test_password
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 5432:5432

      redis:
        image: redis:7-alpine
        options: >-
          --health-cmd "redis-cli ping"
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5
        ports:
          - 6379:6379

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run Prisma migrations
        run: npx prisma migrate deploy --workspace=backend
        env:
          DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db

      - name: Seed test data
        run: npm run seed:test --workspace=backend
        env:
          DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db

      - name: Run integration tests
        run: npm run test:integration --workspace=backend
        env:
          DATABASE_URL: postgresql://test_user:test_password@localhost:5432/test_db
          REDIS_URL: redis://localhost:6379

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: integration-test-results
          path: backend/test-results/
          retention-days: 7

  test-e2e:
    name: E2E Tests
    runs-on: ubuntu-latest
    timeout-minutes: 30
    needs: build
    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright
        run: npx playwright install --with-deps

      - name: Start services (Docker Compose)
        run: docker-compose -f docker-compose.test.yml up -d

      - name: Wait for services to be ready
        run: |
          timeout 60 bash -c 'until curl -f http://localhost:3000/health; do sleep 2; done'

      - name: Run E2E tests
        run: npm run test:e2e
        env:
          BASE_URL: http://localhost:5173

      - name: Upload Playwright report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

      - name: Stop services
        if: always()
        run: docker-compose -f docker-compose.test.yml down

  security-scan:
    name: Security Scanning
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: build
    steps:
      - uses: actions/checkout@v4

      - name: Run Snyk security scan
        uses: snyk/actions/node@master
        env:
          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
        with:
          args: --severity-threshold=high --fail-on=all

      - name: Run OWASP Dependency Check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'erp-generic'
          path: '.'
          format: 'HTML'
          args: >
            --failOnCVSS 7
            --enableRetired

      - name: Upload dependency check report
        if: always()
        uses: actions/upload-artifact@v3
        with:
          name: dependency-check-report
          path: reports/
          retention-days: 30

  quality-gate:
    name: Quality Gate (SonarQube)
    runs-on: ubuntu-latest
    timeout-minutes: 10
    needs: [test-unit, test-integration]
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0 # Full history for SonarQube

      - name: SonarQube Scan
        uses: sonarsource/sonarqube-scan-action@master
        env:
          SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
          SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
        with:
          args: >
            -Dsonar.projectKey=erp-generic
            -Dsonar.qualitygate.wait=true

      - name: Check Quality Gate status
        run: |
          STATUS=$(curl -s -u ${{ secrets.SONAR_TOKEN }}: \
            "${{ secrets.SONAR_HOST_URL }}/api/qualitygates/project_status?projectKey=erp-generic" \
            | jq -r '.projectStatus.status')

          if [ "$STATUS" != "OK" ]; then
            echo "Quality Gate failed: $STATUS"
            exit 1
          fi

  notify-success:
    name: Notify Success
    runs-on: ubuntu-latest
    needs: [lint, build, test-unit, test-integration, test-e2e, security-scan, quality-gate]
    if: success()
    steps:
      - name: Send Slack notification
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          payload: |
            {
              "text": "✅ CI Pipeline passed for ${{ github.repository }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*CI Pipeline Success* ✅\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}"
                  }
                }
              ]
            }

  notify-failure:
    name: Notify Failure
    runs-on: ubuntu-latest
    needs: [lint, build, test-unit, test-integration, test-e2e, security-scan, quality-gate]
    if: failure()
    steps:
      - name: Send Slack notification
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          payload: |
            {
              "text": "❌ CI Pipeline failed for ${{ github.repository }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*CI Pipeline Failed* ❌\n*Repository:* ${{ github.repository }}\n*Branch:* ${{ github.ref_name }}\n*Commit:* ${{ github.sha }}\n*Author:* ${{ github.actor }}\n*Logs:* <${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}|View Logs>"
                  }
                }
              ]
            }
```

### 3.2 CD Workflow (Deployment to QA)

**File:** `.github/workflows/cd-qa.yml`

```yaml
name: CD - Deploy to QA

on:
  push:
    branches: [develop]
  workflow_dispatch: # Manual trigger

env:
  DOCKER_REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  deploy-qa:
    name: Deploy to QA Environment
    runs-on: ubuntu-latest
    timeout-minutes: 15
    environment:
      name: qa
      url: https://qa.erp-generic.local
    steps:
      - uses: actions/checkout@v4

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.DOCKER_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Backend Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./backend
          file: ./backend/Dockerfile
          push: true
          tags: |
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa-${{ github.sha }}
          cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:qa
          cache-to: type=inline

      - name: Build and push Frontend Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./frontend
          file: ./frontend/Dockerfile
          push: true
          tags: |
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa-${{ github.sha }}
          cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:qa
          cache-to: type=inline

      - name: Deploy to QA server
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.QA_SERVER_HOST }}
          username: ${{ secrets.QA_SERVER_USER }}
          key: ${{ secrets.QA_SERVER_SSH_KEY }}
          script: |
            cd /opt/erp-generic
            docker-compose pull
            docker-compose up -d --no-deps backend frontend
            docker-compose exec -T backend npx prisma migrate deploy
            docker image prune -f

      - name: Wait for services to be ready
        run: |
          timeout 120 bash -c 'until curl -f https://qa.erp-generic.local/health; do sleep 5; done'

      - name: Run smoke tests
        run: |
          curl -f https://qa.erp-generic.local/api/health || exit 1
          # Add more smoke tests here

      - name: Notify deployment success
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          payload: |
            {
              "text": "✅ Deployed to QA: ${{ github.sha }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deployment to QA* ✅\n*Environment:* QA\n*Version:* ${{ github.sha }}\n*URL:* https://qa.erp-generic.local\n*Deployed by:* ${{ github.actor }}"
                  }
                }
              ]
            }
```

### 3.3 CD Workflow (Deployment to Production)

**File:** `.github/workflows/cd-production.yml`

```yaml
name: CD - Deploy to Production

on:
  push:
    tags:
      - 'v*.*.*'
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to deploy (e.g., v1.0.0)'
        required: true

env:
  DOCKER_REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}

jobs:
  deploy-production:
    name: Deploy to Production
    runs-on: ubuntu-latest
    timeout-minutes: 30
    environment:
      name: production
      url: https://erp-generic.com
    steps:
      - uses: actions/checkout@v4

      - name: Extract version
        id: version
        run: |
          if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
            echo "VERSION=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT
          else
            echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
          fi

      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.DOCKER_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}

      - name: Build and push Backend Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./backend
          file: ./backend/Dockerfile
          push: true
          tags: |
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:${{ steps.version.outputs.VERSION }}
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest
          cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-backend:latest
          cache-to: type=inline

      - name: Build and push Frontend Docker image
        uses: docker/build-push-action@v5
        with:
          context: ./frontend
          file: ./frontend/Dockerfile
          push: true
          tags: |
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:${{ steps.version.outputs.VERSION }}
            ${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest
          cache-from: type=registry,ref=${{ env.DOCKER_REGISTRY }}/${{ env.IMAGE_NAME }}-frontend:latest
          cache-to: type=inline

      - name: Create database backup
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.PROD_SERVER_HOST }}
          username: ${{ secrets.PROD_SERVER_USER }}
          key: ${{ secrets.PROD_SERVER_SSH_KEY }}
          script: |
            /opt/erp-generic/scripts/backup-postgres.sh

      - name: Deploy to Production server (Blue-Green)
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.PROD_SERVER_HOST }}
          username: ${{ secrets.PROD_SERVER_USER }}
          key: ${{ secrets.PROD_SERVER_SSH_KEY }}
          script: |
            cd /opt/erp-generic
            ./scripts/deploy-blue-green.sh production ${{ steps.version.outputs.VERSION }}

      - name: Verify deployment
        run: |
          timeout 300 bash -c 'until curl -f https://erp-generic.com/health; do sleep 10; done'

      - name: Run post-deployment tests
        run: |
          npm run test:smoke:production

      - name: Notify deployment success
        uses: slackapi/slack-github-action@v1
        with:
          webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
          payload: |
            {
              "text": "✅ Deployed to Production: ${{ steps.version.outputs.VERSION }}",
              "blocks": [
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Deployment to Production* ✅\n*Environment:* Production\n*Version:* ${{ steps.version.outputs.VERSION }}\n*URL:* https://erp-generic.com\n*Deployed by:* ${{ github.actor }}"
                  }
                },
                {
                  "type": "section",
                  "text": {
                    "type": "mrkdwn",
                    "text": "*Rollback command:*\n```./scripts/rollback.sh ${{ steps.version.outputs.VERSION }}```"
                  }
                }
              ]
            }

      - name: Create GitHub Release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          tag_name: ${{ steps.version.outputs.VERSION }}
          release_name: Release ${{ steps.version.outputs.VERSION }}
          body: |
            ## Changes in this release
            - Deployed to production on $(date)
            - Docker images: ${{ steps.version.outputs.VERSION }}

            ## Rollback
            If issues arise, run: `./scripts/rollback.sh ${{ steps.version.outputs.VERSION }}`
          draft: false
          prerelease: false
```

---

## 4. AUTOMATED TESTING INTEGRATION

**Test Execution Summary:**
- **Unit Tests:** 876 tests (backend: 526, frontend: 350) - ~2 minutes
- **Integration Tests:** 438 tests (API + DB + Components) - ~10 minutes
- **E2E Tests:** 147 tests (Critical flows) - ~25 minutes
- **Total:** 1,461 automated tests

**Coverage Requirements:**
- Line coverage: >80%
- Branch coverage: >75%
- Function coverage: >85%

---

## 5. CODE QUALITY GATES

**SonarQube Quality Gate:**
- **Coverage:** >80%
- **Duplicated Lines:** <3%
- **Code Smells:** <10 per 1000 LOC
- **Bugs:** 0 critical, 0 major
- **Vulnerabilities:** 0 critical, 0 major
- **Security Hotspots:** Reviewed
- **Maintainability Rating:** A or B
- **Reliability Rating:** A
- **Security Rating:** A

---

## 6. SECURITY SCANNING

**Tools:**
- **Snyk:** Dependency vulnerability scanning
- **OWASP Dependency Check:** Known CVE scanning
- **GitGuardian:** Secret scanning
- **Trivy:** Container image scanning

**Fail Criteria:**
- Critical vulnerabilities: Block merge
- High vulnerabilities: Warning (fail after 7 days)
- Medium/Low vulnerabilities: Warning only

---

## 7. DOCKER BUILD & PUSH

**Image Naming Convention:**
```
ghcr.io/your-org/erp-generic-backend:latest
ghcr.io/your-org/erp-generic-backend:v1.0.0
ghcr.io/your-org/erp-generic-backend:qa
ghcr.io/your-org/erp-generic-backend:qa-abc123
```

**Image Scanning:**
```yaml
- name: Scan Docker image
  uses: aquasecurity/trivy-action@master
  with:
    image-ref: ghcr.io/${{ github.repository }}-backend:latest
    format: 'sarif'
    output: 'trivy-results.sarif'
```

---

## 8. DEPLOYMENT AUTOMATION

**Deployment Strategies:**
1. **QA:** Rolling update (zero-downtime)
2. **Staging:** Blue-green deployment
3. **Production:** Blue-green with canary (10% → 50% → 100%)

**Pre-Deployment Checklist:**
- [ ] All tests passing
- [ ] Security scans clean
- [ ] Database backup created
- [ ] Migration scripts reviewed
- [ ] Rollback plan documented
- [ ] On-call team notified

---

## 9. ROLLBACK AUTOMATION

**Automated Rollback Triggers:**
- Health check failures (>5 consecutive)
- Error rate >5% for >5 minutes
- p95 latency >2s for >5 minutes
- Manual trigger via GitHub Actions

---

## 10. NOTIFICATIONS

**Notification Channels:**
- **Slack:** #erp-deployments (all deployments), #erp-alerts (failures)
- **Email:** devops@erp-generic.com (production deployments)
- **PagerDuty:** On-call engineer (production failures)

---

## REFERENCES

- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Docker Build Best Practices](https://docs.docker.com/develop/dev-best-practices/)
- [Deployment Guide](./DEPLOYMENT-GUIDE.md)

---

**Documento:** CI-CD-PIPELINE.md
**Versión:** 1.0
**Total Páginas:** ~16
**Última Actualización:** 2025-11-24
