> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Biblioteca de plantillas

> Plantillas listas para copiar y pegar para lenguajes habituales, registros privados e infraestructura Enterprise.

Plantillas listas para copiar y pegar para lenguajes y casos de uso habituales. Cada plantilla es autosuficiente. Combínalas para crear tu configuración completa.

Para ver un desglose completo de cada campo, consulta la [referencia de plantillas](/es/onboard-devin/environment/blueprint-reference).

<Info>
  **Secretos:** Las plantillas hacen referencia a los secretos mediante `$SECRET_NAME`. Configúralos en la pestaña **Secrets** dentro de cada editor de plantillas antes de usar una plantilla. Nunca incrustes credenciales directamente en tu plantilla.
</Info>

***

<div id="quick-start">
  ## Inicio rápido
</div>

Plantillas mínimas para las configuraciones más habituales. Copia uno, pégalo en el editor de plantillas y listo.

<AccordionGroup>
  <Accordion title="Proyecto Node.js">
    ```yaml theme={null}
    initialize: |
      npm install -g pnpm

    maintenance: |
      pnpm install

    knowledge:
      - name: lint
        contents: |
          Ejecuta `pnpm lint` para comprobar si hay errores.
      - name: test
        contents: |
          Ejecuta `pnpm test` para la suite completa.
    ```
  </Accordion>

  <Accordion title="Proyecto Python">
    ```yaml theme={null}
    initialize: |
      curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance: |
      uv sync

    knowledge:
      - name: lint
        contents: |
          Ejecuta `uv run ruff check .` para hacer linting.
      - name: test
        contents: |
          Ejecuta `uv run pytest` para la suite completa.
    ```
  </Accordion>

  <Accordion title="Full-stack (Node + Python)">
    ```yaml theme={null}
    initialize:
      - name: Instalar pnpm
        run: npm install -g pnpm
      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance:
      - name: Dependencias del frontend
        run: (cd frontend && pnpm install)
      - name: Dependencias del backend
        run: (cd backend && uv sync)

    knowledge:
      - name: structure
        contents: |
          - `frontend/` — app de React (pnpm)
          - `backend/` — API de Python (uv)
      - name: test
        contents: |
          Frontend: cd frontend && pnpm test
          Backend: cd backend && uv run pytest
    ```
  </Accordion>
</AccordionGroup>

***

<div id="repository-blueprints">
  ## Plantillas de repositorios
</div>

Pasos de compilación para cada repositorio, gestión de dependencias y entradas de Knowledge. Configúralos en **Settings > Environment > Blueprints > \[tu repo]**.

<div id="python">
  ### Python
</div>

<Tabs>
  <Tab title="uv (recomendado)">
    Configuración recomendada para proyectos de Python que usan [uv](https://docs.astral.sh/uv/) para gestionar dependencias.

    ```yaml theme={null}
    initialize: |
      curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance: |
      uv sync

    knowledge:
      - name: lint
        contents: |
          uv run ruff check .

          Corrección automática:
          uv run ruff check --fix .

      - name: test
        contents: |
          uv run pytest

      - name: build
        contents: |
          uv run python -m build
    ```
  </Tab>

  <Tab title="pip + venv">
    Configuración tradicional para Python con pip y venv. Úsalo cuando tu proyecto dependa de `requirements.txt`.

    ```yaml theme={null}
    initialize: |
      python3 -m venv .venv

    maintenance: |
      source .venv/bin/activate
      pip install -r requirements.txt

    knowledge:
      - name: lint
        contents: |
          source .venv/bin/activate
          flake8 .

      - name: test
        contents: |
          source .venv/bin/activate
          pytest

      - name: build
        contents: |
          source .venv/bin/activate
          python -m build
    ```
  </Tab>
</Tabs>

<div id="nodejs">
  ### Node.js
</div>

<Tabs>
  <Tab title="npm">
    Configuración estándar de Node.js con npm.

    ```yaml theme={null}
    initialize: |
      nvm install 20
      nvm use 20

    maintenance: |
      npm install

    knowledge:
      - name: lint
        contents: |
          npx eslint .

      - name: test
        contents: |
          npm test

      - name: build
        contents: |
          npm run build
    ```

    <Info>
      Usa `npm install` (no `npm ci`) en `maintenance`. Realiza una actualización incremental, mientras que `npm ci` elimina `node_modules` y reinstala todo desde cero en cada sesión.
    </Info>
  </Tab>

  <Tab title="pnpm">
    Para proyectos que usan pnpm.

    ```yaml theme={null}
    initialize: |
      npm install -g pnpm

    maintenance: |
      pnpm install --frozen-lockfile

    knowledge:
      - name: lint
        contents: |
          pnpm lint

      - name: test
        contents: |
          pnpm test

      - name: build
        contents: |
          pnpm build
    ```
  </Tab>
</Tabs>

<div id="go">
  ### Go
</div>

Configuración estándar de Go con módulos.

```yaml theme={null}
initialize: |
  GO_VERSION=1.23.5
  ARCH=$(dpkg --print-architecture)
  curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
    | sudo tar -xz -C /usr/local
  echo 'export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"' \
    | sudo tee /etc/profile.d/golang.sh > /dev/null

  go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest

maintenance: |
  go mod download

knowledge:
  - name: lint
    contents: |
      golangci-lint run

  - name: test
    contents: |
      go test ./...

  - name: build
    contents: |
      go build ./...
```

<div id="java">
  ### Java
</div>

<Tabs>
  <Tab title="Gradle">
    Configuración de Java con Gradle.

    <Info>JDK 17 viene **preinstalado** en la imagen base de Devin. Omite el paso de instalación de JDK si OpenJDK 17 predeterminado es suficiente.</Info>

    ```yaml theme={null}
    initialize:
      - name: Install JDK 17
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Gradle
        run: |
          GRADLE_VERSION=8.12
          curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
            -o /tmp/gradle.zip
          sudo unzip -qo /tmp/gradle.zip -d /opt
          sudo ln -sf /opt/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle
          rm /tmp/gradle.zip

    maintenance: |
      ./gradlew dependencies

    knowledge:
      - name: lint
        contents: |
          ./gradlew check

      - name: test
        contents: |
          ./gradlew test

      - name: build
        contents: |
          ./gradlew build
    ```
  </Tab>

  <Tab title="Maven">
    Configuración de Java con Maven.

    <Info>JDK 17 viene **preinstalado** en la imagen base de Devin. Omite el paso de instalación de JDK si OpenJDK 17 predeterminado es suficiente.</Info>

    ```yaml theme={null}
    initialize:
      - name: Install JDK 17
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Maven
        run: |
          MAVEN_VERSION=3.9.9
          curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
            | sudo tar -xz -C /opt
          sudo ln -sf /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn

    maintenance: |
      mvn dependency:resolve

    knowledge:
      - name: test
        contents: |
          mvn test

      - name: build
        contents: |
          mvn package
    ```
  </Tab>
</Tabs>

<div id="ruby-on-rails">
  ### Ruby on Rails
</div>

Configuración de Rails con PostgreSQL.

```yaml theme={null}
initialize:
  - name: Install Ruby 3.3
    run: |
      sudo apt-get update -qq
      sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ruby-full libpq-dev postgresql-client

maintenance: |
  bundle install
  rails db:migrate

knowledge:
  - name: lint
    contents: |
      bundle exec rubocop

  - name: test
    contents: |
      bundle exec rspec

  - name: build
    contents: |
      rails assets:precompile
```

<div id="rust">
  ### Rust
</div>

Configuración estándar de Rust con Cargo.

<Info>**Rust** (mediante rustup) y **Cargo** vienen **preinstalados** en la imagen base de Devin. Omite el paso de instalación si la cadena de herramientas estable predeterminada es suficiente. Solo necesitas descargar las dependencias.</Info>

```yaml theme={null}
initialize: |
  curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
  source ~/.cargo/env

maintenance: |
  cargo fetch

knowledge:
  - name: lint
    contents: |
      cargo clippy -- -D warnings

  - name: test
    contents: |
      cargo test

  - name: build
    contents: |
      cargo build --release
```

<div id="monorepos">
  ### Monorepos
</div>

<Tabs>
  <Tab title="Varios lenguajes">
    Monorepo con un frontend en Node.js y un backend en Python. Cada subproyecto tiene sus propias entradas de Knowledge.

    ```yaml theme={null}
    initialize:
      - name: Instalar pnpm
        run: npm install -g pnpm
      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance:
      - name: Instalar dependencias del frontend
        run: (cd packages/frontend && pnpm install)
      - name: Instalar dependencias del backend
        run: (cd packages/backend && uv sync)
      - name: Compilar la biblioteca compartida
        run: (cd packages/shared && pnpm install && pnpm build)

    knowledge:
      - name: structure
        contents: |
          Este es un monorepo con tres paquetes:
          - `packages/frontend` — Aplicación de React (TypeScript, pnpm)
          - `packages/backend` — API en Python (FastAPI, uv)
          - `packages/shared` — Utilidades compartidas de TypeScript (deben compilarse antes que el frontend)
      - name: frontend
        contents: |
          Ejecuta `cd packages/frontend && pnpm dev` para iniciar el servidor de desarrollo.
          Ejecuta `cd packages/frontend && pnpm lint` para ejecutar el linter.
          Ejecuta `cd packages/frontend && pnpm test` para ejecutar las pruebas.
      - name: backend
        contents: |
          Ejecuta `cd packages/backend && uv run uvicorn app.main:app --reload` para iniciar la API.
          Ejecuta `cd packages/backend && uv run ruff check .` para ejecutar el linter.
          Ejecuta `cd packages/backend && uv run pytest` para ejecutar las pruebas.
    ```

    <Tip>
      Usa subshells `(cd dir && command)` en lugar de `cd dir && command` para que el directorio de trabajo se restablezca entre pasos.
    </Tip>
  </Tab>

  <Tab title="Múltiples versiones de JDK">
    Monorepo de Java en el que distintos servicios requieren distintas versiones de JDK. Instala ambos JDK durante la configuración y luego usa entradas de `knowledge` para indicarle a Devin qué `JAVA_HOME` debe usar para cada servicio.

    ```yaml theme={null}
    initialize:
      - name: Instalar JDK 17 (principal)
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Instalar JDK 11 (servicio heredado)
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-11-jdk-headless

    maintenance:
      - name: Precargar cachés de dependencias
        run: |
          export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
          (cd services/api && ./gradlew dependencies --refresh-dependencies)

          export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
          (cd services/legacy && ./gradlew dependencies --refresh-dependencies)

    knowledge:
      - name: build_api
        contents: |
          Compilar el servicio de API (JDK 17):
            JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
            cd services/api && ./gradlew clean build
      - name: build_legacy
        contents: |
          Compilar el servicio heredado (JDK 11):
            JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 \
            cd services/legacy && ./gradlew clean build
      - name: test_all
        contents: |
          Ejecutar las pruebas de todos los servicios:
            JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 \
            (cd services/api && ./gradlew test)

            JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 \
            (cd services/legacy && ./gradlew test)
    ```
  </Tab>
</Tabs>

***

<div id="private-package-registries">
  ## Registros privados de paquetes
</div>

Configura los gestores de paquetes para resolver dependencias desde registros privados. Configúralos en **Settings > Environment > Blueprints > configuración de la organización** (o por repositorio si solo un repositorio lo necesita).

<Warning>
  **La configuración de credenciales corresponde a `maintenance`, no a `initialize`.** Los pasos que escriben secretos (contraseñas del registro, tokens de autenticación) en archivos de configuración deben usar `maintenance` para que las credenciales se
  vuelvan a cargar en cada sesión. Los secretos se eliminan antes de guardar la instantánea, por lo que los archivos de configuración escritos durante `initialize` no tendrán credenciales válidas cuando se inicien las sesiones.
</Warning>

<Info>
  Si tu registro privado usa una CA corporativa, asegúrate de que el [certificado de CA](#corporate-ca-certificate) esté instalado primero a nivel de Enterprise. La siguiente configuración asume que la confianza en HTTPS ya
  está establecida.
</Info>

<div id="nodejs-registries">
  ### Registros de Node.js
</div>

<Tabs>
  <Tab title="npm (scoped)">
    Configura npm para resolver paquetes con ámbito (p. ej., `@myorg/*`) desde un registro privado, mientras que los paquetes públicos se siguen obteniendo del registro predeterminado de npm.

    <Accordion title="Secretos requeridos">- `GITHUB_PACKAGES_TOKEN` — Personal Access Token o token de GitHub App con ámbito `read:packages`</Accordion>

    ```yaml theme={null}
    maintenance:
      - name: Configure npm scoped registry
        run: |
          npm config set @myorg:registry https://npm.pkg.github.com
          npm config set //npm.pkg.github.com/:_authToken $GITHUB_PACKAGES_TOKEN
    ```

    <Tip>
      Sustituye `@myorg` por tu ámbito de npm. URLs habituales de registros privados:

      * **GitHub Packages:** `https://npm.pkg.github.com`
      * **Artifactory:** `https://artifactory.example.com/artifactory/api/npm/npm-virtual`
      * **Nexus:** `https://nexus.example.com/repository/npm-group`
      * **GitLab:** `https://gitlab.example.com/api/v4/packages/npm`
      * **AWS CodeArtifact:** `https://<domain>.d.codeartifact.<region>.amazonaws.com/npm/<repo>`
    </Tip>
  </Tab>

  <Tab title="npm (réplica completa)">
    Redirige **todos** los paquetes de npm a través de tu registro privado (no solo los paquetes con ámbito).

    <Accordion title="Secretos requeridos">
      * `NPM_REGISTRY_URL` — URL completa de tu registro de npm (p. ej., `https://artifactory.example.com/artifactory/api/npm/npm-virtual`) - `NPM_REGISTRY_HOST` — Solo el hostname, sin protocolo (p. ej.,
        `artifactory.example.com`) - `REGISTRY_TOKEN` — token de autenticación de npm para el registro
    </Accordion>

    ```yaml theme={null}
    maintenance:
      - name: Configure npm to use private registry
        run: |
          npm config set registry $NPM_REGISTRY_URL
          npm config set //${NPM_REGISTRY_HOST}/:_authToken $REGISTRY_TOKEN
          npm config set strict-ssl true
    ```
  </Tab>

  <Tab title="pnpm">
    Configura pnpm para resolver paquetes desde un registro privado.

    <Accordion title="Secretos requeridos">
      * `NPM_REGISTRY_URL` — URL completa de tu registro de npm - `NPM_REGISTRY_HOST` — Solo el hostname, sin protocolo - `REGISTRY_TOKEN` — token de autenticación de npm para el registro
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install pnpm
        run: npm install -g pnpm

    maintenance:
      - name: Configure pnpm for private registry
        run: |
          pnpm config set registry $NPM_REGISTRY_URL
          pnpm config set //${NPM_REGISTRY_HOST}/:_authToken $REGISTRY_TOKEN
    ```
  </Tab>

  <Tab title="Yarn">
    Configura Yarn (Classic v1 o Berry v2+) para resolver paquetes desde un registro privado.

    <Accordion title="Secretos requeridos">- `NPM_REGISTRY_URL` — URL completa de tu registro de npm/Yarn - `REGISTRY_TOKEN` — token de autenticación para el registro (solo Berry)</Accordion>

    **Yarn Classic (v1):**

    ```yaml theme={null}
    initialize:
      - name: Install Yarn Classic
        run: npm install -g yarn

    maintenance:
      - name: Configure Yarn for private registry
        run: |
          yarn config set registry "$NPM_REGISTRY_URL"
          # Para paquetes con ámbito:
          # yarn config set @myorg:registry "https://npm.pkg.github.com"
    ```

    **Yarn Berry (v2+):**

    ```yaml theme={null}
    maintenance:
      - name: Configure Yarn Berry for private registry
        run: |
          yarn config set npmRegistryServer "$NPM_REGISTRY_URL"
          yarn config set npmAuthToken "$REGISTRY_TOKEN"
          # Para paquetes con ámbito:
          # yarn config set npmScopes.myorg.npmRegistryServer "https://npm.pkg.github.com"
          # yarn config set npmScopes.myorg.npmAuthToken "$GITHUB_PACKAGES_TOKEN"
    ```
  </Tab>
</Tabs>

<div id="python-registries">
  ### Registros de Python
</div>

<Tabs>
  <Tab title="pip / uv">
    Configura pip y uv para resolver paquetes desde tu registro privado de PyPI (p. ej., Nexus, Artifactory).

    <Accordion title="Secretos requeridos">
      * `PYPI_REGISTRY_URL` — URL completa de tu índice de PyPI, incluidas las credenciales si son necesarias (p. ej., `https://user:token@nexus.example.com/repository/pypi-proxy/simple`)
    </Accordion>

    ```yaml theme={null}
    maintenance:
      - name: Configure pip/uv for private registry
        run: |
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = $PYPI_REGISTRY_URL
          EOF

          echo "export UV_INDEX_URL=$PYPI_REGISTRY_URL" \
            | sudo tee /etc/profile.d/uv-registry.sh > /dev/null
    ```

    <Tip>
      Patrones habituales de URL para registros de PyPI:

      * **Artifactory:** `https://artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple`
      * **Nexus:** `https://nexus.example.com/repository/pypi-proxy/simple`
      * **AWS CodeArtifact:** `https://aws:TOKEN@domain-owner.d.codeartifact.region.amazonaws.com/pypi/repo/simple/`
      * **Azure Artifacts:** `https://pkgs.dev.azure.com/org/project/_packaging/feed/pypi/simple`
      * **GitLab:** `https://gitlab.example.com/api/v4/groups/<group-id>/-/packages/pypi/simple`
    </Tip>
  </Tab>

  <Tab title="Poetry">
    Configura Poetry para resolver paquetes desde un registro privado de PyPI.

    <Accordion title="Secretos requeridos">
      * `POETRY_REGISTRY_URL` — URL completa de tu registro compatible con PyPI - `REGISTRY_USER` — nombre de usuario del registro - `REGISTRY_PASS` — contraseña del registro o token de API
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install Poetry
        run: curl -sSL https://install.python-poetry.org | python3 -

    maintenance:
      - name: Configure Poetry for private registry
        run: |
          poetry config repositories.private "$POETRY_REGISTRY_URL"
          poetry config http-basic.private "$REGISTRY_USER" "$REGISTRY_PASS"
    ```
  </Tab>
</Tabs>

<div id="jvm-registries">
  ### Registro de JVM
</div>

<Tabs>
  <Tab title="Maven">
    Instala el JDK y configura Maven para redirigir toda la resolución de dependencias a través de tu registro privado (p. ej., Artifactory, Nexus).

    <Info>JDK 17 está **preinstalado** en la imagen base de Devin. Omite el paso de instalación si el OpenJDK 17 predeterminado es suficiente. Solo necesitas instalar Maven y configurar el registro.</Info>

    <Accordion title="Secretos requeridos">
      * `MAVEN_REGISTRY_URL` — URL de tu registro de Maven (p. ej., `https://artifactory.example.com/artifactory/maven-virtual`) - `REGISTRY_USER` — Nombre de usuario del registro - `REGISTRY_PASS` — Contraseña del registro o
        token de API
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install JDK 17
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Maven
        run: |
          MAVEN_VERSION=3.9.9
          curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
            | sudo tar -xz -C /opt
          sudo ln -sf /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn

    maintenance:
      - name: Configure Maven for private registry
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <mirrors>
              <mirror>
                <id>private-registry</id>
                <mirrorOf>*</mirrorOf>
                <url>$MAVEN_REGISTRY_URL</url>
              </mirror>
            </mirrors>
            <servers>
              <server>
                <id>private-registry</id>
                <username>$REGISTRY_USER</username>
                <password>$REGISTRY_PASS</password>
              </server>
            </servers>
          </settings>
          EOF
    ```

    <Tip>
      Patrones comunes de URL de registros para Maven:

      * **Artifactory:** `https://artifactory.example.com/artifactory/maven-virtual`
      * **Nexus:** `https://nexus.example.com/repository/maven-public`
      * **Azure Artifacts:** `https://pkgs.dev.azure.com/org/project/_packaging/feed/maven/v1`
      * **GitHub Packages:** `https://maven.pkg.github.com`
      * **GitLab:** `https://gitlab.example.com/api/v4/groups/<group-id>/-/packages/maven`
      * **AWS CodeArtifact:** `https://<domain>.d.codeartifact.<region>.amazonaws.com/maven/<repo>`
    </Tip>
  </Tab>

  <Tab title="Gradle">
    Instala el JDK y configura Gradle para resolver todas las dependencias a través de tu registro privado.

    <Info>JDK 17 viene **preinstalado** en la imagen base de Devin. Omite el paso de instalación del JDK si el predeterminado es suficiente.</Info>

    <Accordion title="Secretos requeridos">- `GRADLE_REGISTRY_URL` — URL de tu registro de Gradle/Maven - `REGISTRY_USER` — Nombre de usuario del registro - `REGISTRY_PASS` — Contraseña del registro o token de API</Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install JDK 17
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Gradle
        run: |
          GRADLE_VERSION=8.12
          curl -fsSL "https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip" \
            -o /tmp/gradle.zip
          sudo unzip -qo /tmp/gradle.zip -d /opt
          sudo ln -sf /opt/gradle-${GRADLE_VERSION}/bin/gradle /usr/local/bin/gradle
          rm /tmp/gradle.zip

    maintenance:
      - name: Configure Gradle for private registry
        run: |
          mkdir -p ~/.gradle
          cat > ~/.gradle/init.gradle << EOF
          allprojects {
              repositories {
                  maven {
                      url "$GRADLE_REGISTRY_URL"
                      credentials {
                          username = "$REGISTRY_USER"
                          password = "$REGISTRY_PASS"
                      }
                      allowInsecureProtocol = false
                  }
              }
          }
          EOF
    ```
  </Tab>
</Tabs>

<div id="other-registries">
  ### Otros registros
</div>

<AccordionGroup>
  <Accordion title="proxy para módulos de Go">
    Instala Go y configúralo para resolver módulos mediante un proxy privado de módulos (p. ej., Athens, Artifactory o un endpoint de GOPROXY).

    <Accordion title="Secretos requeridos">
      * `GO_PROXY_URL` — URL de tu proxy de módulos de Go (p. ej., `https://athens.corp.internal`)
      * `GIT_TOKEN` — token de acceso personal para repositorios de Git privados que alojan módulos de Go
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install Go
        run: |
          GO_VERSION=1.23.5
          ARCH=$(dpkg --print-architecture)
          curl -fsSL "https://go.dev/dl/go${GO_VERSION}.linux-${ARCH}.tar.gz" \
            | sudo tar -xz -C /usr/local
          echo 'export PATH="/usr/local/go/bin:$HOME/go/bin:$PATH"' \
            | sudo tee /etc/profile.d/golang.sh > /dev/null

      - name: Configure Go for private modules
        run: |
          cat << 'GOENV' | sudo tee /etc/profile.d/go-private.sh > /dev/null
          export GOPROXY="$GO_PROXY_URL,direct"
          export GONOSUMCHECK="corp.internal/*,github.com/myorg/*"
          export GOPRIVATE="corp.internal/*,github.com/myorg/*"
          GOENV

    maintenance:
      - name: Authenticate Go private modules
        run: |
          git config --global url."https://$GIT_TOKEN@github.com/myorg/".insteadOf "https://github.com/myorg/"
    ```

    <Tip>
      Patrones de URL habituales para proxies de Go:

      * **Artifactory:** `https://artifactory.example.com/artifactory/go-virtual`
      * **Nexus:** `https://nexus.example.com/repository/go-proxy`
      * **Athens:** `https://athens.corp.internal`
    </Tip>
  </Accordion>

  <Accordion title=".NET / NuGet">
    Configura NuGet para resolver paquetes desde un feed privado.

    <Accordion title="Secretos requeridos">
      * `NUGET_SOURCE_URL` — URL de tu feed de NuGet
      * `NUGET_API_KEY` — API key o PAT del feed
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install .NET SDK
        run: |
          curl -fsSL https://dot.net/v1/dotnet-install.sh | bash -s -- --channel 8.0
          echo 'export PATH="$HOME/.dotnet:$PATH"' \
            | sudo tee /etc/profile.d/dotnet.sh > /dev/null

    maintenance:
      - name: Configure NuGet for private feed
        run: |
          dotnet nuget add source "$NUGET_SOURCE_URL" \
            --name private \
            --username any \
            --password "$NUGET_API_KEY" \
            --store-password-in-clear-text 2>/dev/null || \
          dotnet nuget update source private \
            --source "$NUGET_SOURCE_URL" \
            --username any \
            --password "$NUGET_API_KEY" \
            --store-password-in-clear-text
    ```
  </Accordion>

  <Accordion title="Docker">
    Configura Docker para descargar desde un registro privado de contenedores.

    <Accordion title="Secretos requeridos">
      * `DOCKER_MIRROR_URL` *(opcional)* — URL de tu mirror de Docker Hub (p. ej., `https://mirror.corp.internal`)
      * `DOCKER_REGISTRY_URL` — URL de tu registro privado de contenedores (p. ej., `registry.corp.internal:5000`)
      * `DOCKER_REGISTRY_USER` — Nombre de usuario del registro
      * `DOCKER_REGISTRY_PASS` — Contraseña del registro o token de API
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Create Docker config directory
        run: sudo mkdir -p /etc/docker

    maintenance:
      - name: Configure Docker for private registry
        run: |
          # Configurar el mirror del registro (opcional — enruta las descargas de Docker Hub a través de tu registro)
          cat << EOF | sudo tee /etc/docker/daemon.json > /dev/null
          {
            "registry-mirrors": ["$DOCKER_MIRROR_URL"]
          }
          EOF
          sudo systemctl restart docker || true

          # Iniciar sesión en el registro de contenedores privado
          echo "$DOCKER_REGISTRY_PASS" | docker login "$DOCKER_REGISTRY_URL" \
            --username "$DOCKER_REGISTRY_USER" \
            --password-stdin
    ```

    <Tip>
      URLs habituales de registros de contenedores:

      * **Amazon ECR:** `<account-id>.dkr.ecr.<region>.amazonaws.com`
      * **Azure Container Registry:** `<name>.azurecr.io`
      * **Google Artifact Registry:** `<region>-docker.pkg.dev`
      * **GitHub Container Registry:** `ghcr.io`
      * **GitLab Container Registry:** `registry.gitlab.example.com`
      * **Nexus:** `https://nexus.example.com:8443`
      * **JFrog:** `<name>.jfrog.io`
    </Tip>
  </Accordion>

  <Accordion title="Rust / Cargo">
    Configura Cargo para resolver crates desde un registro privado.

    <Info>
      **Rust** (a través de rustup) y **Cargo** vienen **preinstalados** en la imagen base de Devin. Omite el paso de instalación si el toolchain estable predeterminado es suficiente. Solo necesitas configurar el registro.
    </Info>

    <Accordion title="Secretos requeridos">
      * `CARGO_REGISTRY_INDEX` — URL del índice del registro privado (p. ej., `sparse+https://cargo.corp.internal/api/v1/crates/`)
      * `CARGO_REGISTRY_TOKEN` — Token de autenticación para el registro privado
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install Rust
        run: |
          curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
            | sh -s -- -y --default-toolchain stable
          echo 'source "$HOME/.cargo/env"' \
            | sudo tee /etc/profile.d/rust.sh > /dev/null

    maintenance:
      - name: Configure Cargo for private registry
        run: |
          mkdir -p ~/.cargo
          cat > ~/.cargo/config.toml << EOF
          [registries.private]
          index = "$CARGO_REGISTRY_INDEX"
          token = "$CARGO_REGISTRY_TOKEN"

          [source.crates-io]
          replace-with = "private"

          [source.private]
          registry = "$CARGO_REGISTRY_INDEX"
          EOF
    ```

    <Tip>
      Si solo necesitas **agregar** un registro privado sin sustituir crates.io, elimina las secciones `[source.crates-io]` y `[source.private]` y usa `cargo install --registry private` o `[dependencies] my-crate = { version = "1.0", registry = "private" }` en `Cargo.toml`.
    </Tip>
  </Accordion>

  <Accordion title="Ruby / Bundler">
    Instala Ruby y configura Bundler para resolver las gemas desde un servidor privado de gemas.

    <Accordion title="Secretos requeridos">
      * `GEM_SERVER_URL` — URL de tu servidor privado de gemas (p. ej., `https://artifactory.example.com/artifactory/api/gems/gems-virtual`)
      * `REGISTRY_USER` — Nombre de usuario del registro
      * `REGISTRY_PASS` — Contraseña o token de API del registro
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install Ruby
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq ruby-full

    maintenance:
      - name: Configure Bundler for private gem server
        run: |
          bundle config set mirror.https://rubygems.org "$GEM_SERVER_URL"
          bundle config set "$GEM_SERVER_URL" "$REGISTRY_USER:$REGISTRY_PASS"
    ```

    <Tip>
      Patrones de URL habituales para servidores de gemas:

      * **Artifactory:** `https://artifactory.example.com/artifactory/api/gems/gems-virtual`
      * **Nexus:** `https://nexus.example.com/repository/rubygems-proxy`
      * **Gemfury:** `https://gem.fury.io/<org>`
    </Tip>
  </Accordion>

  <Accordion title="PHP / Composer">
    Instala PHP y configura Composer para resolver paquetes desde un registro privado de Packagist o Satis.

    <Accordion title="Secretos requeridos">
      * `COMPOSER_REGISTRY_URL` — URL de tu registro privado de Composer (p. ej., `https://repo.packagist.com/<org>`)
      * `REGISTRY_USER` — Nombre de usuario del registro
      * `REGISTRY_PASS` — Contraseña del registro o token de la API
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install PHP and Composer
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
            php-cli php-mbstring php-xml php-curl unzip

          # Instalar Composer
          curl -sS https://getcomposer.org/installer | php
          sudo mv composer.phar /usr/local/bin/composer

    maintenance:
      - name: Configure Composer for private registry
        run: |
          composer config --global repositories.private \
            composer "$COMPOSER_REGISTRY_URL"

          # Autenticarse con el registro
          composer config --global http-basic.$(echo "$COMPOSER_REGISTRY_URL" \
            | sed 's|https\?://||;s|/.*||') "$REGISTRY_USER" "$REGISTRY_PASS"
    ```

    <Tip>
      Patrones de URL comunes para registros de Composer:

      * **Artifactory:** `https://artifactory.example.com/artifactory/api/composer/packagist-virtual`
      * **Nexus:** `https://nexus.example.com/repository/packagist-proxy`
      * **Private Packagist:** `https://repo.packagist.com/<org>`
      * **Satis:** `https://satis.corp.internal`
    </Tip>
  </Accordion>

  <Accordion title="Actualización del token de AWS CodeArtifact">
    Los tokens de AWS CodeArtifact caducan al cabo de 12 horas. Usa `maintenance` para configurar el comando de renovación del token, de modo que el agente pueda volver a ejecutarlo cuando sea necesario. Este ejemplo configura npm, pip y Maven para usar CodeArtifact.

    <Info>
      **`awscli` viene preinstalado** en la imagen base de Devin. Solo necesitas renovar el token y configurar el registro.
    </Info>

    <Accordion title="Secretos requeridos">
      * `AWS_ACCESS_KEY_ID` y `AWS_SECRET_ACCESS_KEY` — credenciales de IAM con los permisos `codeartifact:GetAuthorizationToken` y `sts:GetServiceBearerToken`
      * `CA_DOMAIN` — nombre de tu dominio de CodeArtifact
      * `CA_DOMAIN_OWNER` — ID de la cuenta de AWS propietaria del dominio
      * `CA_REGION` — región de AWS (p. ej., `us-east-1`)
      * `CA_NPM_REPO`, `CA_PYPI_REPO`, `CA_MAVEN_REPO` — nombres de los repositorios de cada ecosistema
    </Accordion>

    ```yaml theme={null}
    maintenance:
      - name: Refresh CodeArtifact auth token
        run: |
          # Obtener un token nuevo (válido por 12 horas)
          export CODEARTIFACT_AUTH_TOKEN=$(aws codeartifact get-authorization-token \
            --domain $CA_DOMAIN \
            --domain-owner $CA_DOMAIN_OWNER \
            --region $CA_REGION \
            --query authorizationToken \
            --output text)

          CA_ENDPOINT="https://${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com"

          # Configurar npm
          npm config set registry "${CA_ENDPOINT}/npm/${CA_NPM_REPO}/"
          npm config set "//${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com/npm/${CA_NPM_REPO}/:_authToken" "$CODEARTIFACT_AUTH_TOKEN"

          # Configurar pip
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = https://aws:${CODEARTIFACT_AUTH_TOKEN}@${CA_DOMAIN}-${CA_DOMAIN_OWNER}.d.codeartifact.${CA_REGION}.amazonaws.com/pypi/${CA_PYPI_REPO}/simple/
          EOF

          # Configurar Maven (opcional)
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <servers>
              <server>
                <id>codeartifact</id>
                <username>aws</username>
                <password>${CODEARTIFACT_AUTH_TOKEN}</password>
              </server>
            </servers>
            <mirrors>
              <mirror>
                <id>codeartifact</id>
                <mirrorOf>*</mirrorOf>
                <url>${CA_ENDPOINT}/maven/${CA_MAVEN_REPO}/</url>
              </mirror>
            </mirrors>
          </settings>
          EOF
    ```
  </Accordion>
</AccordionGroup>

***

<div id="enterprise-infrastructure">
  ## Infraestructura de Enterprise
</div>

Infraestructura a nivel de máquina que se aplica a todas las organizaciones y repositorios. Configúrala en **Settings > entorno base de Devin** (a nivel Enterprise) o **Settings > Environment > Blueprints > configuración de la organización** (a nivel de organización).

<div id="network-and-connectivity">
  ### Red y conectividad
</div>

<AccordionGroup>
  <Accordion title="Certificado de autoridad de certificación corporativa">
    Tu organización usa una autoridad certificadora privada para los servicios internos. Devin necesita el certificado raíz para conectarse a los repositorios y herramientas internos mediante HTTPS.

    <Accordion title="Secretos requeridos">- `CORP_ROOT_CA_B64` — Certificado PEM codificado en Base64 de tu CA corporativa. Genéralo con: `cat corp-root-ca.crt | base64 -w0`</Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install corporate CA certificate
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          sudo update-ca-certificates
          echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
            | sudo tee /etc/profile.d/node-ca.sh > /dev/null
    ```
  </Accordion>

  <Accordion title="Múltiples certificados de CA">
    Si tu organización usa múltiples certificados de CA (p. ej., CAs separadas para distintos servicios internos).

    <Accordion title="Secretos requeridos">- `CORP_ROOT_CA_B64` — certificado de CA principal codificado en Base64 - `CORP_INTERMEDIATE_CA_B64` — certificado de CA intermedio codificado en Base64</Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install corporate CA certificates
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          echo "$CORP_INTERMEDIATE_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-intermediate-ca.crt > /dev/null
          sudo update-ca-certificates

          # Crear un paquete combinado para herramientas que necesitan un único archivo CA
          cat /usr/local/share/ca-certificates/corp-*.crt \
            | sudo tee /usr/local/share/ca-certificates/corp-bundle.crt > /dev/null

          echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-bundle.crt' \
            | sudo tee /etc/profile.d/node-ca.sh > /dev/null
    ```
  </Accordion>

  <Accordion title="Proxy HTTP/HTTPS">
    Dirige todo el tráfico de red a través de un proxy corporativo.

    <Accordion title="Secretos requeridos">
      * `CORP_HTTP_PROXY` — URL del proxy HTTP (p. ej., `http://proxy.corp.example.com:8080`) - `CORP_HTTPS_PROXY` — URL del proxy HTTPS - `CORP_NO_PROXY` — Lista de hosts separados por comas para omitir el proxy (p. ej.,
        `localhost,127.0.0.1,.corp.example.com`)
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Configure system-wide proxy
        run: |
          cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
          export http_proxy="$CORP_HTTP_PROXY"
          export https_proxy="$CORP_HTTPS_PROXY"
          export no_proxy="$CORP_NO_PROXY"
          export HTTP_PROXY="$CORP_HTTP_PROXY"
          export HTTPS_PROXY="$CORP_HTTPS_PROXY"
          export NO_PROXY="$CORP_NO_PROXY"
          PROXY
          source /etc/profile.d/proxy.sh

    maintenance:
      - name: Configure git proxy
        run: |
          git config --global http.proxy "$CORP_HTTP_PROXY"
          git config --global https.proxy "$CORP_HTTPS_PROXY"

          # Configurar proxy de npm
          npm config set proxy "$CORP_HTTP_PROXY"
          npm config set https-proxy "$CORP_HTTPS_PROXY"
    ```
  </Accordion>

  <Accordion title="Proxy con autenticación">
    Si tu proxy corporativo requiere autenticación con nombre de usuario y contraseña.

    <Accordion title="Secretos requeridos">
      * `PROXY_USER` — Nombre de usuario del proxy - `PROXY_PASS` — Contraseña del proxy - `PROXY_HOST` — Host y puerto del proxy (p. ej., `proxy.corp.example.com:8080`) - `CORP_NO_PROXY` — Hosts que deben omitir el proxy
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Configure authenticated proxy
        run: |
          PROXY_URL="http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}"

          cat << PROXY | sudo tee /etc/profile.d/proxy.sh > /dev/null
          export http_proxy="$PROXY_URL"
          export https_proxy="$PROXY_URL"
          export no_proxy="$CORP_NO_PROXY"
          export HTTP_PROXY="$PROXY_URL"
          export HTTPS_PROXY="$PROXY_URL"
          export NO_PROXY="$CORP_NO_PROXY"
          PROXY
          source /etc/profile.d/proxy.sh

    maintenance:
      - name: Configure git for authenticated proxy
        run: |
          PROXY_URL="http://${PROXY_USER}:${PROXY_PASS}@${PROXY_HOST}"
          git config --global http.proxy "$PROXY_URL"
          git config --global https.proxy "$PROXY_URL"
    ```
  </Accordion>

  <Accordion title="Certificado de la CA + proxy (combinados)">
    Configuración combinada para entornos que necesitan tanto una CA corporativa como un proxy. Esto es habitual en entornos empresariales donde los servicios internos usan certificados privados y todo el tráfico debe pasar por un proxy.

    <Accordion title="Secretos requeridos">
      * `CORP_ROOT_CA_B64` — Certificado de CA corporativa codificado en Base64 - `CORP_HTTP_PROXY`, `CORP_HTTPS_PROXY` — URL del proxy - `CORP_NO_PROXY` — Hosts que deben omitir el proxy
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install corporate CA certificate
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          sudo update-ca-certificates
          echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
            | sudo tee /etc/profile.d/node-ca.sh > /dev/null

      - name: Configure system-wide proxy
        run: |
          cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
          export http_proxy="$CORP_HTTP_PROXY"
          export https_proxy="$CORP_HTTPS_PROXY"
          export no_proxy="$CORP_NO_PROXY"
          export HTTP_PROXY="$CORP_HTTP_PROXY"
          export HTTPS_PROXY="$CORP_HTTPS_PROXY"
          export NO_PROXY="$CORP_NO_PROXY"
          PROXY
          source /etc/profile.d/proxy.sh

    maintenance:
      - name: Configure git proxy
        run: |
          git config --global http.proxy "$CORP_HTTP_PROXY"
          git config --global https.proxy "$CORP_HTTPS_PROXY"
    ```
  </Accordion>

  <Accordion title="Conexión VPN">
    Solo se puede acceder a tus registros privados, servidores Git u otros servicios internos a través de una VPN. Debe ejecutarse **antes** que otros módulos que necesiten acceso de red a recursos internos.

    <Accordion title="Secretos requeridos">
      **OpenVPN:**

      * `VPN_CONFIG_B64` — Archivo de configuración de OpenVPN codificado en Base64 (`.ovpn`). Genéralo con: `cat corp.ovpn | base64 -w0`
      * `VPN_AUTH_USER` *(opcional)* — Nombre de usuario de la VPN, si tu VPN requiere autenticación con nombre de usuario y contraseña
      * `VPN_AUTH_PASS` *(opcional)* — Contraseña de la VPN

      **WireGuard:**

      * `WG_CONFIG_B64` — Archivo de configuración de WireGuard codificado en Base64. Genéralo con: `cat wg0.conf | base64 -w0`
    </Accordion>

    **OpenVPN:**

    ```yaml theme={null}
    initialize:
      - name: Install and configure OpenVPN
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openvpn

          # Escribir la configuración de VPN
          sudo mkdir -p /etc/openvpn/client
          echo "$VPN_CONFIG_B64" | base64 -d \
            | sudo tee /etc/openvpn/client/corp.conf > /dev/null

          # Si la VPN requiere autenticación con usuario/contraseña
          if [ -n "${VPN_AUTH_USER:-}" ] && [ -n "${VPN_AUTH_PASS:-}" ]; then
            printf '%s\n%s\n' "$VPN_AUTH_USER" "$VPN_AUTH_PASS" \
              | sudo tee /etc/openvpn/client/auth.txt > /dev/null
            sudo chmod 600 /etc/openvpn/client/auth.txt
            echo "auth-user-pass /etc/openvpn/client/auth.txt" \
              | sudo tee -a /etc/openvpn/client/corp.conf > /dev/null
          fi

          # Iniciar el túnel VPN
          sudo systemctl daemon-reload
          sudo systemctl enable --now openvpn-client@corp

          # Esperar a que el túnel se establezca
          for i in $(seq 1 30); do
            if ip link show tun0 >/dev/null 2>&1; then break; fi
            sleep 1
          done
    ```

    **WireGuard:**

    ```yaml theme={null}
    initialize:
      - name: Install and configure WireGuard
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq wireguard-tools

          # Escribir la configuración de WireGuard
          echo "$WG_CONFIG_B64" | base64 -d \
            | sudo tee /etc/wireguard/wg0.conf > /dev/null
          sudo chmod 600 /etc/wireguard/wg0.conf

          # Iniciar el túnel
          sudo systemctl enable --now wg-quick@wg0
    ```

    <Tip>
      Para más información sobre la configuración de la VPN, consulta [VPN Configuration](/es/onboard-devin/vpn).
    </Tip>
  </Accordion>

  <Accordion title="Resolución de DNS personalizada">
    Tus servicios internos usan nombres DNS privados que no se pueden resolver mediante DNS público.

    ```yaml theme={null}
    initialize:
      - name: Configure custom DNS resolution
        run: |
          # Agregar nombres de host internos
          cat << 'HOSTS' | sudo tee -a /etc/hosts > /dev/null
          10.0.1.50  nexus.corp.internal
          10.0.1.51  git.corp.internal
          10.0.1.52  artifactory.corp.internal
          HOSTS

          # Opcionalmente, configurar servidores de nombres personalizados
          sudo mkdir -p /etc/systemd/resolved.conf.d
          cat << 'DNS' | sudo tee /etc/systemd/resolved.conf.d/corp.conf > /dev/null
          [Resolve]
          DNS=10.0.0.53 10.0.0.54
          Domains=corp.internal
          DNS
          sudo systemctl restart systemd-resolved || true
    ```
  </Accordion>
</AccordionGroup>

<div id="identity-and-security">
  ### Identidad y seguridad
</div>

<AccordionGroup>
  <Accordion title="Firma de commits con GPG">
    Tu organización requiere que todos los commits de Git estén firmados y quieres que GitHub marque los commits de Devin como **Verified**.

    <Accordion title="Secretos requeridos">
      * `GPG_PRIVATE_KEY_B64` — Clave privada de GPG codificada en Base64. Genérala con: `gpg --export-secret-keys <key-id> | base64 -w0`
      * `GIT_USER_NAME` — Nombre del autor de Git (p. ej., `Devin AI`)
      * `GIT_USER_EMAIL` — Correo electrónico del autor de Git. **Debe coincidir con un UID de la clave GPG**; de lo contrario, GitHub no verificará la firma.
    </Accordion>

    <Note>
      Sube también la clave **pública** correspondiente a la cuenta de GitHub cuyas credenciales usa Devin para hacer push (en [GitHub Settings > SSH and GPG keys](https://github.com/settings/keys)). GitHub solo marca los commits como Verified cuando la clave pública de firma está registrada en la cuenta que creó el commit.
    </Note>

    ```yaml theme={null}
    initialize:
      - name: Prepare GPG and git signing config
        run: |
          # Permitir que GPG funcione sin una TTY
          echo 'export GPG_TTY=$(tty)' | sudo tee -a /etc/profile.d/gpg.sh > /dev/null

    maintenance:
      - name: Import GPG key and configure git signing
        run: |
          echo "$GPG_PRIVATE_KEY_B64" | base64 -d | gpg --batch --import 2>/dev/null

          KEY_ID=$(gpg --list-secret-keys --keyid-format long 2>/dev/null \
            | grep sec | head -1 | awk '{print $2}' | cut -d'/' -f2)

          git config --global user.signingkey "$KEY_ID"
          git config --global commit.gpgsign true
          git config --global tag.gpgsign true
          git config --global gpg.program gpg
    ```
  </Accordion>

  <Accordion title="Identidad de Git y claves SSH">
    Configura la identidad de Git y las claves SSH de Devin para acceder a servidores Git privados.

    <Accordion title="Secretos requeridos">
      * `GIT_USER_NAME` — Nombre del autor de Git - `GIT_USER_EMAIL` — Correo electrónico del autor de Git - `SSH_PRIVATE_KEY_B64` — Clave privada SSH codificada en Base64. Genérala con: `cat ~/.ssh/id_ed25519 | base64 -w0` -
        `SSH_KNOWN_HOSTS_B64` — Entradas de hosts conocidos codificadas en Base64. Genéralas con: `ssh-keyscan git.corp.internal | base64 -w0` - `SSH_CONFIG_B64` *(opcional)* — Archivo de configuración SSH codificado en Base64
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Configure git identity
        run: |
          git config --global user.name "$GIT_USER_NAME"
          git config --global user.email "$GIT_USER_EMAIL"

          # Preparar el directorio SSH
          mkdir -p ~/.ssh && chmod 700 ~/.ssh

    maintenance:
      - name: Install SSH keys
        run: |
          # Instalar la clave privada SSH (en maintenance para que se vuelva a cargar en cada sesión)
          echo "$SSH_PRIVATE_KEY_B64" | base64 -d > ~/.ssh/id_ed25519
          chmod 600 ~/.ssh/id_ed25519

          # Agregar hosts conocidos para tu servidor Git
          echo "$SSH_KNOWN_HOSTS_B64" | base64 -d >> ~/.ssh/known_hosts

          # Instalar opcionalmente una configuración SSH personalizada
          if [ -n "${SSH_CONFIG_B64:-}" ]; then
            echo "$SSH_CONFIG_B64" | base64 -d > ~/.ssh/config
            chmod 600 ~/.ssh/config
          fi
    ```

    <Tip>
      Genera la entrada de hosts conocidos para tu servidor Git con `ssh-keyscan git.corp.internal | base64 -w0`.
    </Tip>
  </Accordion>
</AccordionGroup>

<div id="system-configuration">
  ### Configuración del sistema
</div>

<AccordionGroup>
  <Accordion title="Paquetes del sistema">
    Instale paquetes del sistema que no estén en la imagen predeterminada de Devin (p. ej., bibliotecas nativas para el procesamiento de imágenes o la generación de PDF).

    ```yaml theme={null}
    initialize:
      - name: Install system packages
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq \
            libpq-dev \
            libmagickwand-dev \
            poppler-utils \
            ffmpeg
    ```
  </Accordion>

  <Accordion title="Variables de entorno personalizadas">
    Establece variables de entorno persistentes que deben estar disponibles en cada sesión.

    La forma recomendada es escribir líneas `KEY=VALUE` en el archivo `$ENVRC`. Las variables escritas en `$ENVRC` se exportan automáticamente para todos los pasos posteriores y para la sesión de Devin (de forma similar a `$GITHUB_ENV` de GitHub Actions).

    ```yaml theme={null}
    initialize:
      - name: Set custom environment variables
        run: |
          echo "CORPORATE_ENV=production" >> $ENVRC
          echo "DEFAULT_REGION=us-east-1" >> $ENVRC
          echo "MAX_RETRIES=3" >> $ENVRC
    ```

    <Tip>
      También puedes escribir variables de entorno en scripts de `/etc/profile.d/` para que estén disponibles en todo el sistema:

      ```bash theme={null}
      cat << 'ENVVARS' | sudo tee /etc/profile.d/custom-env.sh > /dev/null
      export CORPORATE_ENV=production
      export DEFAULT_REGION=us-east-1
      ENVVARS
      ```

      Ambos métodos funcionan. `$ENVRC` es más sencillo y se recomienda en la mayoría de los casos.
    </Tip>
  </Accordion>

  <Accordion title="Configuración regional y zona horaria">
    Las imágenes base predeterminadas pueden tener la configuración regional mal configurada. Configure la configuración regional y la zona horaria para evitar advertencias de las herramientas de compilación, Java, Python y Git.

    ```yaml theme={null}
    initialize:
      - name: Configure locale and timezone
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq locales

          # Generar y establecer la configuración regional
          sudo sed -i 's/^# *en_US.UTF-8/en_US.UTF-8/' /etc/locale.gen
          sudo locale-gen
          sudo update-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8

          cat << 'LOCALE' | sudo tee /etc/profile.d/locale.sh > /dev/null
          export LANG="en_US.UTF-8"
          export LC_ALL="en_US.UTF-8"
          LOCALE

          # Establecer la zona horaria
          sudo timedatectl set-timezone UTC 2>/dev/null || \
            sudo ln -sfn /usr/share/zoneinfo/UTC /etc/localtime
    ```
  </Accordion>

  <Accordion title="Límites de recursos (ulimits)">
    Las compilaciones de Java, Gradle y Node.js suelen alcanzar el límite predeterminado de 1024 archivos abiertos. Auméntelo para evitar fallos de compilación.

    ```yaml theme={null}
    initialize:
      - name: Raise resource limits
        run: |
          cat << 'LIMITS' | sudo tee /etc/security/limits.d/99-devin.conf > /dev/null
          *    soft    nofile    65536
          *    hard    nofile    65536
          *    soft    nproc     65536
          *    hard    nproc     65536
          LIMITS

          # También establecer el máximo del kernel
          echo "fs.file-max = 65536" | sudo tee /etc/sysctl.d/99-devin-filemax.conf > /dev/null
          sudo sysctl -p /etc/sysctl.d/99-devin-filemax.conf 2>/dev/null || true
    ```
  </Accordion>

  <Accordion title="Reemplazo del mirror de APT">
    En entornos aislados o restringidos, sustituye los repositorios APT predeterminados de Ubuntu por un mirror interno.

    <Accordion title="Secretos requeridos">- `APT_MIRROR_URL` — URL de tu mirror interno de APT (p. ej., `https://artifactory.example.com/artifactory/ubuntu-remote`)</Accordion>

    ```yaml theme={null}
    initialize:
      - name: Replace APT sources with internal mirror
        run: |
          # Hacer copia de seguridad de las fuentes originales
          sudo cp /etc/apt/sources.list /etc/apt/sources.list.bak

          # Reemplazar todos los mirrors de Ubuntu con tu mirror interno
          sudo sed -i "s|http://archive.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
          sudo sed -i "s|http://security.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list

          sudo apt-get update -qq
    ```

    <Tip>
      Patrones comunes de URL para mirrors de APT:

      * **Artifactory:** `https://artifactory.example.com/artifactory/ubuntu-remote`
      * **Nexus:** `https://nexus.example.com/repository/ubuntu-proxy`
    </Tip>
  </Accordion>
</AccordionGroup>

***

<div id="advanced-patterns">
  ## Patrones avanzados
</div>

<AccordionGroup>
  <Accordion title="Variables de entorno con direnv">
    El Environment base de Devin incluye [direnv](https://direnv.net/). Usa `initialize` para crear archivos `.envrc`. Direnv los carga automáticamente.

    ```yaml theme={null}
    initialize: |
      cat <<'EOF' > .envrc
      export DATABASE_URL=postgresql://localhost:5432/myapp_dev
      export REDIS_URL=redis://localhost:6379
      export APP_ENV=development
      EOF

    maintenance: |
      direnv allow .
    ```

    <Info>
      direnv ya viene integrado en el shell de Devin, por lo que las variables de `.envrc` se cargan automáticamente. No necesitas hacer `source` manualmente.
    </Info>

    <Tip>
      Para las variables de entorno **sensibles** (API keys, tokens, contraseñas de bases de datos), usa [secretos del repositorio](/es/product-guides/secrets) en lugar de archivos `.envrc`. Los secretos del repositorio se almacenan de forma segura y se inyectan durante la sesión. Nunca aparecen en tu blueprint ni en tu instantánea.
    </Tip>
  </Accordion>

  <Accordion title="Cambio de versión de Node según el repositorio">
    Usa nvm (preinstalado) para cambiar la versión de Node.js de cada repositorio mediante `.nvmrc`.

    ```yaml theme={null}
    initialize: |
      nvm install 18
      nvm install 20
      nvm install 22

    maintenance: |
      nvm use
    ```

    <Info>
      `nvm use` lee el archivo `.nvmrc` de la raíz del repositorio. Asegúrate de que tu repositorio incluya ese archivo (p. ej., con `20`).
    </Info>
  </Accordion>

  <Accordion title="Autenticación en el navegador (Playwright)">
    Devin proporciona un navegador Chrome con un endpoint de CDP en `localhost:29229` **durante las sesiones**. Usa scripts de Playwright para automatizar el inicio de sesión en el navegador.

    <Warning>El navegador solo está disponible durante las sesiones, no en las compilaciones de instantánea. Instala Playwright en `initialize` y mantén los scripts de inicio de sesión en tu repositorio.</Warning>

    ```yaml theme={null}
    initialize: |
      pip install playwright
      playwright install chromium

    maintenance: |
      npm install

    knowledge:
      - name: browser-auth
        contents: |
          This project requires browser authentication.
          Run the login script before interacting with the app:
          python scripts/login.py

          Devin's Chrome browser is accessible via CDP at:
          http://localhost:29229
    ```

    Script de ejemplo para iniciar sesión (`scripts/login.py`):

    ```python theme={null}
    from playwright.sync_api import sync_playwright
    import os

    with sync_playwright() as p:
        browser = p.chromium.connect_over_cdp("http://localhost:29229")
        context = browser.contexts[0]
        page = context.pages[0] if context.pages else context.new_page()

        page.goto("https://internal-tool.example.com/login")
        page.fill("#username", os.environ["TOOL_USERNAME"])
        page.fill("#password", os.environ["TOOL_PASSWORD"])
        page.click('button[type="submit"]')
        page.wait_for_url("**/dashboard")
    ```

    <Warning>
      Guarda las credenciales de inicio de sesión como secretos, no en el código fuente. Para mantener la autenticación a largo plazo, confirma los scripts de inicio de sesión en `.agents/skills/` para que Devin pueda autenticarse de nuevo automáticamente.
    </Warning>
  </Accordion>

  <Accordion title="Herramientas personalizadas del sistema y PATH">
    Instala paquetes del sistema, binarios personalizados y configura la variable PATH en `initialize`.

    ```yaml theme={null}
    initialize:
      - name: Install system packages
        run: |
          apt-get update
          apt-get install -y \
            jq \
            ripgrep \
            fd-find \
            protobuf-compiler \
            libssl-dev

      - name: Install custom CLI tool
        run: |
          curl -L https://github.com/example/tool/releases/download/v1.0/tool-linux-amd64 \
            -o /usr/local/bin/mytool
          chmod +x /usr/local/bin/mytool

      - name: Add custom bin directory to PATH
        run: |
          mkdir -p ~/bin
          echo 'export PATH="$HOME/bin:$PATH"' >> ~/.bashrc

    knowledge:
      - name: tools
        contents: |
          Custom tools available:
          - mytool: installed at /usr/local/bin/mytool
          - Additional binaries can be placed in ~/bin
    ```
  </Accordion>

  <Accordion title="GitHub Actions para configurar herramientas">
    Devin permite ejecutar GitHub Actions basadas en Node.js directamente en blueprints. Esto es útil para instalar versiones específicas de herramientas mediante las mismas acciones que usa tu CI.

    ```yaml theme={null}
    initialize:
      - name: "Install Node.js 20"
        uses: github.com/actions/setup-node@v4
        with:
          node-version: "20"

      - name: "Install Python 3.12"
        uses: github.com/actions/setup-python@v5
        with:
          python-version: "3.12"

      - name: "Install Go 1.22"
        uses: github.com/actions/setup-go@v5
        with:
          go-version: "1.22"

      - name: "Install Java 21"
        uses: github.com/actions/setup-java@v4
        with:
          java-version: "21"
          distribution: "temurin"

      - name: "Install Gradle"
        uses: github.com/gradle/actions/setup-gradle@v4

      - name: "Install Ruby 3.3"
        uses: github.com/ruby/setup-ruby@v1
        with:
          ruby-version: "3.3"

      - name: "Install additional tools"
        run: |
          pip install uv
          npm install -g pnpm turbo
          go install golang.org/x/tools/gopls@latest

    maintenance: |
      echo "All runtimes are available:"
      node --version
      python --version
      go version
      java --version
    ```

    <Info>
      Acciones como `setup-node` y `setup-python` modifican PATH y las variables de entorno. Los binarios instalados por una acción están disponibles en todos los pasos posteriores y en `maintenance`. Solo se admiten las GitHub Actions **basadas en Node.js**. Las acciones compuestas y basadas en Docker no son compatibles.
    </Info>

    <Tip>
      No necesitas GitHub Actions para la configuración básica de herramientas. Los comandos directos de shell (`nvm install 20`, `curl ... | sh`, `apt-get install`) funcionan igual de bien y suelen ser más sencillos. Las GitHub Actions resultan más útiles cuando quieres reproducir exactamente tu configuración de CI o necesitas la comodidad de acciones como `setup-java`, que gestionan múltiples distribuciones.
    </Tip>
  </Accordion>

  <Accordion title="Proxy inverso HTTPS local para múltiples apps">
    Ejecuta varios servicios detrás de hostnames de apariencia real mediante HTTPS, como `app.example.com`, `api.example.com` y `admin.example.com`. Instala un único proxy inverso en `initialize` y enruta cada hostname a un puerto upstream local distinto.

    [Caddy](https://caddyserver.com/) gestiona el enrutamiento y el TLS local en una sola herramienta. Un Caddyfile asigna cada hostname a un upstream, y `tls internal` emite automáticamente un certificado de confianza para cada hostname desde la CA integrada de Caddy. `caddy trust` instala esa raíz de CA en el almacén de confianza del sistema, y agregar la misma raíz a la base de datos NSS permite que el navegador la acepte.

    Sube tu Caddyfile mediante la sección de **Archivos adjuntos** del editor de blueprint; después estará disponible como `$FILE_CADDYFILE`.

    ```caddyfile Caddyfile theme={null}
    app.example.com {
      tls internal
      reverse_proxy 127.0.0.1:3000
    }

    api.example.com {
      tls internal
      reverse_proxy 127.0.0.1:3001
    }

    admin.example.com {
      tls internal
      reverse_proxy 127.0.0.1:3002
    }
    ```

    ```yaml theme={null}
    initialize:
      - name: Install Caddy and NSS tools
        run: |
          sudo apt-get install -y debian-keyring debian-archive-keyring \
            apt-transport-https curl libnss3-tools
          curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
            | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
          curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
            | sudo tee /etc/apt/sources.list.d/caddy-stable.list
          sudo apt-get update -qq
          sudo apt-get install -y caddy

      - name: Install Caddyfile, hosts entries, and trust internal CA
        run: |
          sudo install -m 0644 "$FILE_CADDYFILE" /etc/caddy/Caddyfile

          for host in app.example.com api.example.com admin.example.com; do
            grep -q " $host$" /etc/hosts \
              || echo "127.0.0.1 $host" | sudo tee -a /etc/hosts > /dev/null
          done

          sudo systemctl enable --now caddy
          sudo caddy trust

          mkdir -p ~/.pki/nssdb
          [ -f ~/.pki/nssdb/cert9.db ] \
            || certutil -d sql:$HOME/.pki/nssdb -N --empty-password

          CADDY_ROOT=/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt
          for _ in $(seq 1 30); do
            sudo test -f "$CADDY_ROOT" && break
            sleep 1
          done

          if sudo test -f "$CADDY_ROOT" \
             && ! certutil -d sql:$HOME/.pki/nssdb -L | grep -q "Caddy Local Authority"; then
            sudo install -o "$USER" -m 0644 "$CADDY_ROOT" /tmp/caddy-root.crt
            certutil -d sql:$HOME/.pki/nssdb -A -t "C,," \
              -n "Caddy Local Authority" -i /tmp/caddy-root.crt
            rm -f /tmp/caddy-root.crt
          fi
    ```

    <Info>
      El mapeo de `/etc/hosts` es lo que hace que `app.example.com` se resuelva como `127.0.0.1` dentro de la sesión. Agrega una entrada por cada hostname que incluyas en el Caddyfile.
    </Info>

    <Tip>
      Para agregar un servicio, añade un bloque de tres líneas al Caddyfile y una entrada al mapeo de `/etc/hosts`, y luego visita su hostname por HTTPS. Caddy emite un certificado en la primera solicitud, así que no hace falta generar certificados por aplicación.
    </Tip>
  </Accordion>
</AccordionGroup>

***

<div id="full-stack-examples">
  ## Ejemplos de full stack
</div>

Estos ejemplos muestran cómo se combinan las configuraciones de Enterprise y a nivel de org. En la práctica, se dividirían entre distintos ámbitos. Se muestran juntas aquí como referencia.

<AccordionGroup>
  <Accordion title="Stack empresarial completo (Artifactory)">
    Un Environment empresarial completo: certificado CA corporativo, proxy, Java (Maven), Python (pip/uv), Node.js (npm) y Docker, todos apuntando a una única instancia de Artifactory.

    <Accordion title="Secretos requeridos">
      **Red y confianza (para toda la cuenta):**

      * `CORP_ROOT_CA_B64` — certificado de la CA corporativa codificado en Base64
      * `CORP_HTTP_PROXY` — URL del proxy HTTP
      * `CORP_HTTPS_PROXY` — URL del proxy HTTPS
      * `CORP_NO_PROXY` — hosts que deben omitir el proxy

      **Credenciales del registro (para toda la organización):**

      * `ARTIFACTORY_USER` — nombre de usuario de Artifactory
      * `ARTIFACTORY_TOKEN` — token de API o contraseña de Artifactory
      * `ARTIFACTORY_MAVEN_URL` — URL del repositorio de Maven (p. ej., `https://artifactory.example.com/artifactory/maven-virtual`)
      * `ARTIFACTORY_PYPI_URL` — URL del repositorio de PyPI (p. ej., `https://user:token@artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple`)
      * `ARTIFACTORY_NPM_URL` — URL del repositorio de npm (p. ej., `https://artifactory.example.com/artifactory/api/npm/npm-virtual`)
      * `ARTIFACTORY_DOCKER_URL` — URL del registro de Docker (p. ej., `artifactory.example.com`)
    </Accordion>

    Esto normalmente se dividiría en tres ámbitos:

    * **Para toda la cuenta (`initialize`):** Certificado y proxy
    * **En toda la organización (`initialize`):** Instalación del entorno de ejecución del lenguaje
    * **Para toda la organización (`maintenance`):** Credenciales del registro (se actualizan durante las compilaciones y se ponen a disposición del agente al inicio de la sesión)

    Se muestra aquí de forma combinada como referencia:

    ```yaml theme={null}
    initialize:
      # ── Para toda la cuenta: red y confianza ──────────────────────────────────────

      - name: Install corporate CA certificate
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          sudo update-ca-certificates
          echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
            | sudo tee /etc/profile.d/node-ca.sh > /dev/null

      - name: Configure system-wide proxy
        run: |
          cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
          export http_proxy="$CORP_HTTP_PROXY"
          export https_proxy="$CORP_HTTPS_PROXY"
          export no_proxy="$CORP_NO_PROXY"
          export HTTP_PROXY="$CORP_HTTP_PROXY"
          export HTTPS_PROXY="$CORP_HTTPS_PROXY"
          export NO_PROXY="$CORP_NO_PROXY"
          PROXY
          source /etc/profile.d/proxy.sh

      # ── Para toda la organización: entornos de ejecución ──────────────────────────────────────────

      - name: Install JDK 17 + Maven
        run: |
          sudo apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

          MAVEN_VERSION=3.9.9
          curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
            | sudo tar -xz -C /opt
          sudo ln -sf /opt/apache-maven-${MAVEN_VERSION}/bin/mvn /usr/local/bin/mvn

      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance:
      # ── Para toda la cuenta: proxy de git (se actualiza en cada sesión) ───────────────────

      - name: Configure git proxy
        run: |
          git config --global http.proxy "$CORP_HTTP_PROXY"
          git config --global https.proxy "$CORP_HTTPS_PROXY"

      # ── Para toda la organización: credenciales de registro (se actualizan en cada sesión) ──────────────

      - name: Configure Maven → Artifactory
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <mirrors>
              <mirror>
                <id>artifactory</id>
                <mirrorOf>*</mirrorOf>
                <url>$ARTIFACTORY_MAVEN_URL</url>
              </mirror>
            </mirrors>
            <servers>
              <server>
                <id>artifactory</id>
                <username>$ARTIFACTORY_USER</username>
                <password>$ARTIFACTORY_TOKEN</password>
              </server>
            </servers>
          </settings>
          EOF

      - name: Configure pip/uv → Artifactory PyPI
        run: |
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = $ARTIFACTORY_PYPI_URL
          trusted-host = $(echo "$ARTIFACTORY_PYPI_URL" | sed 's|https\?://||;s|/.*||')
          EOF

          echo "export UV_INDEX_URL=$ARTIFACTORY_PYPI_URL" \
            | sudo tee /etc/profile.d/uv-registry.sh > /dev/null

      - name: Configure npm → Artifactory
        run: |
          npm config set registry "$ARTIFACTORY_NPM_URL"
          REGISTRY_HOST=$(echo "$ARTIFACTORY_NPM_URL" | sed 's|https\?://||;s|/.*||')
          npm config set "//${REGISTRY_HOST}/:_authToken" "$ARTIFACTORY_TOKEN"

      - name: Configure Docker → Artifactory
        run: |
          echo "$ARTIFACTORY_TOKEN" | docker login "$ARTIFACTORY_DOCKER_URL" \
            --username "$ARTIFACTORY_USER" \
            --password-stdin
    ```

    <Info>
      En este ejemplo, todos los registries apuntan a la misma instancia de Artifactory, pero usan rutas de URL diferentes. Cada ecosistema de paquetes tiene su propio formato de endpoint. Las URL de Maven, PyPI, npm y Docker son diferentes incluso para el mismo registry.
    </Info>
  </Accordion>

  <Accordion title="Varios lenguajes con distintos repositorios">
    Cuando distintos lenguajes usan diferentes repositorios privados (p. ej., Maven desde Nexus, npm desde GitHub Packages, Python desde Artifactory).

    <Accordion title="Secretos requeridos">
      * `NEXUS_MAVEN_URL` — URL del repositorio Maven de Nexus
      * `NEXUS_USER` — nombre de usuario de Nexus
      * `NEXUS_PASS` — contraseña de Nexus
      * `GITHUB_PACKAGES_TOKEN` — token de acceso personal de GitHub con el ámbito `read:packages`
      * `ARTIFACTORY_USER` — nombre de usuario de Artifactory
      * `ARTIFACTORY_TOKEN` — token de API de Artifactory
      * `GIT_TOKEN` — token de acceso personal para módulos privados de Go
    </Accordion>

    ```yaml theme={null}
    maintenance:
      # Maven → Nexus
      - name: Configure Maven → Nexus
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <mirrors>
              <mirror>
                <id>nexus</id>
                <mirrorOf>*</mirrorOf>
                <url>$NEXUS_MAVEN_URL</url>
              </mirror>
            </mirrors>
            <servers>
              <server>
                <id>nexus</id>
                <username>$NEXUS_USER</username>
                <password>$NEXUS_PASS</password>
              </server>
            </servers>
          </settings>
          EOF

      # npm → GitHub Packages (con ámbito)
      - name: Configure npm → GitHub Packages
        run: |
          npm config set @myorg:registry https://npm.pkg.github.com
          npm config set //npm.pkg.github.com/:_authToken $GITHUB_PACKAGES_TOKEN

      # Python → Artifactory
      - name: Configure pip → Artifactory
        run: |
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = https://$ARTIFACTORY_USER:$ARTIFACTORY_TOKEN@artifactory.example.com/artifactory/api/pypi/pypi-virtual/simple
          EOF

      # Go → módulos privados mediante git
      - name: Configure Go private modules
        run: |
          git config --global url."https://$GIT_TOKEN@github.com/myorg/".insteadOf "https://github.com/myorg/"
    ```
  </Accordion>

  <Accordion title="Entorno aislado de la red con réplicas privadas">
    En un entorno completamente aislado de la red, Devin no puede acceder a ninguna URL pública. Todas las herramientas, los entornos de ejecución y los paquetes deben provenir de mirrors internos.

    <Accordion title="Secretos requeridos">
      **Certificados:**

      * `CORP_ROOT_CA_B64` — Certificado de CA corporativa codificado en Base64

      **Acceso al mirror:**

      * `APT_MIRROR_URL` — URL del mirror interno de Ubuntu APT
      * `MIRROR_USER` — Nombre de usuario para autenticarse en el mirror
      * `MIRROR_PASS` — Contraseña para autenticarse en el mirror
      * `JDK_TARBALL_URL` — URL para descargar el archivo tar del JDK desde el mirror interno
      * `NODE_TARBALL_URL` — URL para descargar el archivo tar de Node.js desde el mirror interno

      **Registros de paquetes:**

      * `INTERNAL_MAVEN_URL` — URL del registro interno de Maven
      * `INTERNAL_NPM_URL` — URL del registro interno de npm
      * `INTERNAL_PYPI_URL` — URL del registro interno de PyPI
    </Accordion>

    ```yaml theme={null}
    initialize:
      - name: Install corporate CA certificate
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          sudo update-ca-certificates

      - name: Replace apt sources with internal mirror
        run: |
          sudo sed -i "s|http://archive.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
          sudo sed -i "s|http://security.ubuntu.com/ubuntu|$APT_MIRROR_URL|g" /etc/apt/sources.list
          sudo apt-get update -qq

      - name: Install JDK from internal mirror
        run: |
          # Descargar el tarball de JDK desde el almacén de artefactos interno
          curl -fsSL -u "$MIRROR_USER:$MIRROR_PASS" "$JDK_TARBALL_URL" \
            | sudo tar -xz -C /usr/local
          sudo ln -sf /usr/local/jdk-17.*/bin/java /usr/local/bin/java
          sudo ln -sf /usr/local/jdk-17.*/bin/javac /usr/local/bin/javac
          echo "export JAVA_HOME=$(ls -d /usr/local/jdk-17.*)" \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Node.js from internal mirror
        run: |
          curl -fsSL -u "$MIRROR_USER:$MIRROR_PASS" "$NODE_TARBALL_URL" \
            | sudo tar -xz -C /usr/local --strip-components=1

    maintenance:
      - name: Configure all package managers for internal registry
        run: |
          # Maven
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <mirrors>
              <mirror>
                <id>internal</id>
                <mirrorOf>*</mirrorOf>
                <url>$INTERNAL_MAVEN_URL</url>
              </mirror>
            </mirrors>
            <servers>
              <server>
                <id>internal</id>
                <username>$MIRROR_USER</username>
                <password>$MIRROR_PASS</password>
              </server>
            </servers>
          </settings>
          EOF

          # npm
          npm config set registry "$INTERNAL_NPM_URL"

          # pip
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = $INTERNAL_PYPI_URL
          EOF
    ```

    <Warning>
      En entornos aislados de la red, todas las herramientas que Devin necesita (entornos de ejecución, herramientas de CLI, etc.) deben estar disponibles en tus repositorios espejo internos. No se puede acceder a los repositorios públicos ni a los sitios de descarga.
    </Warning>
  </Accordion>

  <Accordion title="VPN + certificados + proxy + lenguajes">
    Una configuración empresarial completa que combina conectividad VPN con certificados, proxy y compatibilidad con varios idiomas. Este es el orden de operaciones recomendado.

    <Accordion title="Secretos requeridos">
      **VPN:**

      * `VPN_CONFIG_B64` — Archivo de configuración de OpenVPN codificado en Base64

      **Red y confianza:**

      * `CORP_ROOT_CA_B64` — Certificado de la CA corporativa codificado en Base64
      * `CORP_HTTP_PROXY` — URL del proxy HTTP
      * `CORP_HTTPS_PROXY` — URL del proxy HTTPS
      * `CORP_NO_PROXY` — Hosts excluidos del proxy

      **Credenciales del registro:**

      * `MAVEN_REGISTRY_URL` — URL del registro de Maven
      * `NPM_REGISTRY_URL` — URL del registro de npm
      * `PYPI_REGISTRY_HOST` — Nombre de host del registro de PyPI
      * `REGISTRY_USER` — Nombre de usuario del registro (para Maven y pip)
      * `REGISTRY_PASS` — Contraseña del registro (para Maven y pip)
      * `REGISTRY_TOKEN` — Token de autenticación de npm
    </Accordion>

    ```yaml theme={null}
    initialize:
      # 1. VPN — debe ir primero para que los recursos internos sean accesibles
      - name: Establish VPN connection
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get update -qq
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openvpn
          sudo mkdir -p /etc/openvpn/client
          echo "$VPN_CONFIG_B64" | base64 -d \
            | sudo tee /etc/openvpn/client/corp.conf > /dev/null
          sudo systemctl daemon-reload
          sudo systemctl enable --now openvpn-client@corp
          for i in $(seq 1 30); do
            if ip link show tun0 >/dev/null 2>&1; then break; fi
            sleep 1
          done

      # 2. DNS — resolver nombres de host internos
      - name: Configure DNS
        run: |
          sudo mkdir -p /etc/systemd/resolved.conf.d
          cat << 'DNS' | sudo tee /etc/systemd/resolved.conf.d/corp.conf > /dev/null
          [Resolve]
          DNS=10.0.0.53
          Domains=corp.internal
          DNS
          sudo systemctl restart systemd-resolved || true

      # 3. Certificados — confiar en las CAs internas
      - name: Install CA certificate
        run: |
          echo "$CORP_ROOT_CA_B64" | base64 -d \
            | sudo tee /usr/local/share/ca-certificates/corp-root-ca.crt > /dev/null
          sudo update-ca-certificates
          echo 'export NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/corp-root-ca.crt' \
            | sudo tee /etc/profile.d/node-ca.sh > /dev/null

      # 4. Proxy — enrutar el tráfico a través del proxy corporativo
      - name: Configure proxy
        run: |
          cat << 'PROXY' | sudo tee /etc/profile.d/proxy.sh > /dev/null
          export http_proxy="$CORP_HTTP_PROXY"
          export https_proxy="$CORP_HTTPS_PROXY"
          export no_proxy="$CORP_NO_PROXY"
          export HTTP_PROXY="$CORP_HTTP_PROXY"
          export HTTPS_PROXY="$CORP_HTTPS_PROXY"
          export NO_PROXY="$CORP_NO_PROXY"
          PROXY
          source /etc/profile.d/proxy.sh

      # 5. Entornos de ejecución
      - name: Install JDK 17
        run: |
          sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq openjdk-17-jdk-headless
          echo 'export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64' \
            | sudo tee /etc/profile.d/java.sh > /dev/null

      - name: Install Node.js tooling
        run: npm install -g pnpm

      - name: Install uv
        run: curl -LsSf https://astral.sh/uv/install.sh | sh

    maintenance:
      - name: Configure git proxy
        run: |
          git config --global http.proxy "$CORP_HTTP_PROXY"
          git config --global https.proxy "$CORP_HTTPS_PROXY"

      - name: Configure Maven
        run: |
          mkdir -p ~/.m2
          cat > ~/.m2/settings.xml << EOF
          <settings>
            <mirrors>
              <mirror>
                <id>corp</id>
                <mirrorOf>*</mirrorOf>
                <url>$MAVEN_REGISTRY_URL</url>
              </mirror>
            </mirrors>
            <servers>
              <server>
                <id>corp</id>
                <username>$REGISTRY_USER</username>
                <password>$REGISTRY_PASS</password>
              </server>
            </servers>
          </settings>
          EOF

      - name: Configure npm
        run: |
          npm config set registry "$NPM_REGISTRY_URL"
          NPM_HOST=$(echo "$NPM_REGISTRY_URL" | sed 's|https\?://||;s|/.*||')
          npm config set "//${NPM_HOST}/:_authToken" "$REGISTRY_TOKEN"

      - name: Configure pip/uv
        run: |
          mkdir -p ~/.config/pip
          cat > ~/.config/pip/pip.conf << EOF
          [global]
          index-url = https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple
          EOF
          echo "export UV_INDEX_URL=https://$REGISTRY_USER:$REGISTRY_PASS@${PYPI_REGISTRY_HOST}/simple" \
            | sudo tee /etc/profile.d/uv-registry.sh > /dev/null
    ```

    <Info>
      **El orden de los pasos de `initialize` es importante.** La VPN debe ir primero (para que los hosts internos sean accesibles), luego el DNS (para que los nombres se resuelvan), después los certificados (para que HTTPS funcione), luego el proxy (para que el tráfico se enrute correctamente) y, por último, los entornos de ejecución, que pueden descargarse desde mirrors internos).
    </Info>
  </Accordion>
</AccordionGroup>

***

<div id="tips-for-writing-good-blueprints">
  ## Consejos para escribir buenas plantillas
</div>

* **Prueba primero los comandos en una sesión.** Ejecuta los comandos manualmente en una sesión de Devin antes de agregarlos a tu plantilla. Es más rápido que esperar un ciclo completo de compilación.
* **Usa `initialize` para herramientas que se instalan una sola vez y `maintenance` para dependencias.** Todo lo que tarde varios minutos en instalarse (compiladores, binarios grandes, herramientas globales) debe ir en `initialize`. Los comandos rápidos de dependencias (`npm install`, `uv sync`) van en `maintenance`.
* **Haz que los comandos de `maintenance` sean rápidos.** Procura que tarden menos de 2 minutos. Se ejecutan durante las compilaciones y se muestran al agente al inicio de la sesión.
* **Usa `$ENVRC` para las variables de entorno.** No escribas en `.bashrc` ni en `.profile`. `$ENVRC` es el mecanismo compatible para establecer variables entre pasos y sesiones.
* **Pon nombre a tus pasos.** La forma expandida con campos `name` hace que los fallos en los registros de compilación sean mucho más fáciles de identificar.
* **Usa subshells para monorepos.** `(cd packages/foo && npm install)` se ejecuta en una subshell para que los pasos posteriores no se vean afectados por el cambio de directorio.
* **Usa `npm install`, no `npm ci`.** `npm ci` elimina `node_modules` y reinstala todo desde cero, lo que lo hace lento para `maintenance`.
* **Usa secretos del repo para los valores sensibles.** Configúralos en la pestaña **Secrets** del editor de plantilla del repositorio, en lugar de incluirlos directamente en las plantillas.

Para conocer los detalles de la sintaxis, consulta la [referencia de plantilla](/es/onboard-devin/environment/blueprint-reference). Para solucionar fallos de compilación, consulta [Configuración declarativa > Solución de problemas](/es/onboard-devin/environment/blueprints#troubleshooting-builds).
