Estado: Borrador
Versión: v1
Ámbito: Arquitectura propuesta inicial para automatizar el flujo manual de implementaciones VTQA
Sistema: Promotions dentro de Workspace
Backend de ejecución: Jenkins como caja negra, mediante jobs/scripts específicos para Promotions
Storage actual: repo.veritran.com / Nextcloud hospedado por Veritran
Origen de BAP: BAR — BAP Application Repository
Alcance inicial: VTQA
Fuera de alcance inicial: VTPROD, cancelación de ejecuciones, links efímeros, reemplazo de Jenkins
Promotions será una nueva capacidad dentro de Workspace para automatizar y gobernar el flujo de implementaciones que hoy se realiza de forma manual sobre Nextcloud y Jenkins.
El objetivo de v1 es reducir la intervención manual del usuario en la creación de carpetas, preparación de artefactos, edición de steps2run.vt, generación del paquete final y ejecución opcional en ambientes accesibles por Veritran.
En lugar de que el usuario entre manualmente a repo.veritran.com, cree carpetas, suba archivos y luego ejecute Jenkins, Promotions centralizará el flujo desde una UI propia:
bapProps dentro de IMPLE_VTNET_OBJECTS.steps2run.vt.VT-FULL-<IMPLENAME>.tar.gz.VT-FULL ya generado cuando el cliente permita ejecución remota.Jenkins no será modelado internamente por Promotions. Para esta arquitectura, Jenkins queda encapsulado como un execution backend. Promotions solo conocerá qué job invocar, con qué parámetros, cómo consultar estado, cómo leer logs y cómo resolver el paquete generado.
Una decisión clave acordada con el equipo de Jenkins/BT Wizard es desacoplar el flujo legacy run_implementation en dos capacidades específicas para Promotions:
Generate VT-FULL Job
Execute VT-FULL Job
Esto evita que la ejecución vuelva a generar el paquete y permite que cada deployment apunte a un VT-FULL concreto, previamente generado y trazable.
Promotions v1 deberá permitir:
bapProps dentro de IMPLE_VTNET_OBJECTS.steps2run.vt inicial.steps2run.vt desde la UI.VT-FULL-<IMPLENAME>.tar.gz como contrato portable.La primera versión no busca:
vt-wizard o BT Wizard.VTPROD-PACKAGES.bapProps.IMPLENAME, aunque se recomienda estandarizarla.Promotions no debe depender de detalles internos del Jenkinsfile.
Debe conocer:
No debe modelar internamente cada stage del pipeline.
Generar un paquete y ejecutar un paquete son responsabilidades distintas.
El flujo legacy podía generar el VT-FULL y, al ejecutar, volver a generarlo. Esto no es ideal porque:
Promotions v1 debe usar dos capacidades distintas:
Generate VT-FULL Job
Execute VT-FULL Job
Promotions debe exponer conceptos de negocio:
Jenkins debe verse como una dependencia técnica, no como el modelo de dominio.
El paquete final VT-FULL-<IMPLENAME>.tar.gz debe seguir siendo el contrato portable entre Veritran y los ambientes destino.
Debe poder usarse para:
La arquitectura v1 debe automatizar la preparación manual actual, pero sin exigir cambios profundos en Jenkins ni en vt-wizard.
Las integraciones deben encapsularse detrás de adapters:
BARAdapterNextcloudAdapterJenkinsAdapterWorkspaceAuthAdapterEsto permite reemplazar o evolucionar partes sin impactar el dominio principal.
Toda operación relevante debe emitir eventos de auditoría.
La auditoría no debe depender únicamente de logs Jenkins.
El flujo legacy run_implementation podía operar de esta forma:
EJECUTAR=false
-> generar VT-FULL
EJECUTAR=true
-> generar VT-FULL
-> ejecutar
Eso significa que, si Promotions primero generaba un paquete y luego ejecutaba, la ejecución podía volver a generar el VT-FULL.
Se acuerda crear dos scripts/jobs nuevos para uso de Promotions:
Generate VT-FULL Job
Execute VT-FULL Job
run_implementation.Interfaz de usuario dentro de Workspace.
Responsabilidades:
steps2run.vt.La UI debe ocultar acciones no permitidas según permisos y metadata del cliente.
Ejemplo:
Servicio principal de dominio.
Responsabilidades:
El backend debe contener el modelo de dominio. Jenkins, Nextcloud y BAR son dependencias externas.
Base de datos agnóstica a tecnología en v1.
Responsabilidades:
steps2run.vt.La tecnología específica queda fuera del alcance de esta versión del documento.
Adapter para consumir BAR — BAP Application Repository.
Premisa v1:
BAR devuelve:
.tar.gz.bapProps.Responsabilidades:
bapProps.Interfaz conceptual:
BARAdapter
- getBap(bapId): BapMetadata
- downloadBapArtifact(bapId): BinaryArtifact
- downloadBapProps(bapId): BinaryArtifact
Adapter para interactuar con repo.veritran.com.
Responsabilidades:
IMPLE_VTNET_OBJECTS.bapProps a IMPLE_VTNET_OBJECTS.steps2run.vt.steps2run.vt.steps2run.vt.Interfaz conceptual:
NextcloudAdapter
- createFolder(path)
- uploadFile(path, file)
- writeFile(path, content)
- readFile(path)
- updateFile(path, content)
- listFiles(path)
- exists(path)
- getDownloadLink(path)
En v1, la descarga puede resolverse mediante link directo de Nextcloud.
A futuro, Promotions puede generar links efímeros, proxy de descarga o signed URLs.
Adapter para invocar Jenkins como caja negra.
Responsabilidades:
VT-FULL.VT-FULL ya generado.Interfaz conceptual:
JenkinsAdapter
- triggerFullPackageGeneration(params): JenkinsBuildRef
- triggerFullPackageExecution(params): JenkinsBuildRef
- getBuildStatus(buildRef): BuildStatus
- getBuildLogs(buildRef, cursor): LogChunk
- getBuildUrl(buildRef): URL
- getBuildResult(buildRef): BuildResult
- getJobParameters(jobRef): JenkinsParameterSet
Promotions debe usar getJobParameters como POC para extraer choices dinámicos, por ejemplo nodos. Si no es viable o suficiente, usará metadata JSON por cliente.
Canal de actualización casi realtime desde Promotions hacia la UI.
Decisión v1:
Promotions hace polling contra Jenkins.
Promotions transforma estado/logs en eventos.
Promotions emite eventos SSE hacia la UI.
La UI no consulta Jenkins directamente.
Responsabilidades:
Eventos conceptuales:
package.queued
package.running
package.log
package.succeeded
package.failed
deployment.queued
deployment.running
deployment.log
deployment.step.updated
deployment.succeeded
deployment.failed
Registro persistente de eventos de dominio.
Responsabilidades:
steps2run.vt.Eventos mínimos v1:
implementation.created
implementation.folder_created
steps2run.updated
package.requested
package.started
package.succeeded
package.failed
deployment.requested
deployment.started
deployment.succeeded
deployment.failed
package.download_link_generated
Como no toda la metadata existe de forma centralizada en otro sistema, Promotions guardará un JSON configurable por cliente.
Ejemplo conceptual:
{
"repoClient": "VTPLATFORM",
"repoBasePath": "VTPLATFORM",
"vtqaPackagesPath": "VTQA-PACKAGES",
"supportsRemoteExecution": true,
"defaultEnvironment": "VTQA",
"allowedEnvironments": ["VTQA"],
"nodes": ["NDA1", "NDA2"],
"jenkins": {
"generateJobPath": "PROMOTIONS/generate_vt_full",
"executeJobPath": "PROMOTIONS/execute_vt_full",
"defaultComponent": "CORE"
},
"nextcloud": {
"objectsFolderName": "IMPLE_VTNET_OBJECTS"
}
}
Notas:
VTPLATFORM es solo un ejemplo de cliente/repositorio.repoClient.Promotions estará dentro de Workspace, por lo que la autenticación y autorización se apoyarán en ese componente.
Se propone un modelo granular de permisos.
| Permiso | Descripción |
|---|---|
promotion:view |
Ver listado y detalle de implementaciones. |
promotion:create-folder |
Crear carpeta de implementación en Nextcloud. |
promotion:edit-steps2run |
Editar steps2run.vt. |
promotion:create-package |
Disparar Jenkins para crear VT-FULL. |
promotion:execute |
Disparar ejecución remota en VTQA. |
promotion:download-package |
Generar/ver link de descarga del paquete final. |
promotion:manage-customer-config |
Administrar metadata JSON de clientes. |
promotion:view-logs |
Ver logs o link de logs asociados a Jenkins. |
Roles sugeridos:
| Rol | Permisos típicos |
|---|---|
| Viewer | promotion:view, promotion:view-logs |
| Operator | Viewer + create-folder, edit-steps2run, create-package, download-package |
| Executor | Operator + execute |
| Admin | Todos, incluyendo manage-customer-config |
DRAFT
CREATING_FOLDER
FOLDER_READY
FAILED
PACKAGE_REQUESTED
PACKAGING
PACKAGE_READY
PACKAGE_FAILED
DEPLOYMENT_REQUESTED
DEPLOYING
DEPLOYED
DEPLOYMENT_FAILED
Para simplificar la UI, Promotions puede exponer un estado agregado:
DRAFT
FOLDER_READY
PACKAGING
PACKAGE_READY
DEPLOYING
DEPLOYED
FAILED
Automatizar lo que hoy el usuario hace manualmente en Nextcloud:
IMPLE_VTNET_OBJECTS.bapProps.steps2run.vt.<REPO_CLIENT>/
VTQA-PACKAGES/
<IMPLENAME>/
IMPLE_VTNET_OBJECTS/
steps2run.vt
<bap-artifact>.tar.gz
<bapProps-file>
Donde <REPO_CLIENT> puede ser VTPLATFORM u otro cliente configurado.
steps2run.vtFormato:
<bap-artifact>.tar.gz:<NODE>
Ejemplo:
business-application-pruebas-baps-4.1.1-WebP2-03.tar.gz:NDA1
steps2run.vtEl usuario podrá editar únicamente steps2run.vt.
No podrá editar:
bapProps.Cada edición debe registrar evento:
steps2run.updated
Idealmente guardando:
Generar el paquete final portable:
VT-FULL-<IMPLENAME>.tar.gz
sin ejecutar la implementación.
Generar paquete equivale a disparar Jenkins con:
EJECUTAR=false
Promotions deberá construir los parámetros requeridos por el job.
Conceptualmente:
IMPLENAME=<IMPLENAME>
CUSTOMER=<customer/repo client, según job>
NODE=<node seleccionado o requerido por Jenkins>
COMPONENTE=<component configurado o seleccionado>
EJECUTAR=false
GENERATEDOC=<según decisión/config>
TOKEN=<si aplica>
EXTRAFLAGS=<si aplica>
SKIP=<si aplica>
VTDBRUNIMPL=<si aplica>
ISZIP=<según estrategia>
El set exacto dependerá del contrato final definido con el equipo de Jenkins.
Promotions debe centralizar el mapeo en JenkinsAdapter, no dispersarlo en la UI.
Ejecutar la implementación en VTQA para clientes con ejecución remota habilitada.
La acción de ejecutar solo debe estar disponible si:
customer.supportsRemoteExecution = true
Para clientes on-premise/desconectados, la UI no debe mostrar la acción o debe mostrarla deshabilitada.
Ejecutar paquete equivale a disparar Jenkins con:
EJECUTAR=true
Actualmente se asume que Jenkins genera/prepara el paquete y ejecuta dentro del mismo flujo.
La estrategia será:
Promotions polling Jenkins -> Promotions SSE -> UI
La UI no consultará Jenkins directamente.
event: deployment.step.updated
data: {
"deploymentId": "DEP-20260511-1182",
"step": "connecting_to_jenkins",
"status": "completed"
}
event: deployment.log
data: {
"deploymentId": "DEP-20260511-1182",
"offset": 12048,
"message": "Running VTWizard..."
}
event: deployment.succeeded
data: {
"deploymentId": "DEP-20260511-1182",
"summary": {
"transactions": { "total": 242, "imported": 242, "skipped": 0 },
"responseCodes": { "total": 87, "imported": 87, "skipped": 1 }
}
}
La UI podrá mostrar un resumen estructurado si Jenkins/vt-wizard entrega información parseable desde logs o salida.
Ejemplo conceptual:
{
"transactions": {
"total": 242,
"imported": 242,
"skipped": 0
},
"responseCodes": {
"total": 87,
"imported": 87,
"skipped": 1
},
"businessParameters": {
"total": 121,
"imported": 121,
"skipped": 0
},
"configurationFiles": {
"total": 5,
"imported": 5,
"skipped": 0
}
}
Si la salida no es suficientemente estructurada, v1 debe degradar elegantemente a:
Permitir que el usuario descargue el paquete final generado:
VT-FULL-<IMPLENAME>.tar.gz
En v1, Promotions puede generar o devolver un link directo contra Nextcloud.
Evento de auditoría:
package.download_link_generated
En versiones posteriores:
Clientes on-premise o desconectados no tendrán acción de ejecución remota.
Flujo:
Crear implementación
Generar paquete
Descargar paquete
Entregar paquete al cliente
Cliente descomprime y ejecuta localmente
Clientes con ejecución remota habilitada podrán:
El path base depende del cliente.
Ejemplo:
<REPO_CLIENT>/VTQA-PACKAGES/<IMPLENAME>/IMPLE_VTNET_OBJECTS
Ejemplo concreto visto previamente:
VTPLATFORM/VTQA-PACKAGES/IMPLE_WEB_P2_002/IMPLE_VTNET_OBJECTS
VTPLATFORM no es fijo globalmente; representa un cliente/repositorio y puede cambiar.
Promotions deberá soportar:
create implementation folder
create IMPLE_VTNET_OBJECTS
upload BAP .tar.gz
upload bapProps
write steps2run.vt
read steps2run.vt
update steps2run.vt
list files
resolve package path
resolve download link
Promotions usará Jenkins como caja negra.
Job inicial esperado:
Generate VT-FULL Job
Execute VT-FULL Job
EJECUTAR=false
Resultado esperado:
VT-FULL-<IMPLENAME>.tar.gz publicado en VTQA-PACKAGES
Input conceptual:
Referencia al VT-FULL ya generado
Environment
Node
Component/configuration parameters
Runtime token/flags si aplican
Resultado esperado:
Implementación ejecutada en VTQA
Logs disponibles
Resultado parseable si es posible
Promotions debe construir los parámetros requeridos a partir de:
Ejemplo conceptual para generación:
{
"IMPLENAME": "IMPLE_WEB_P2_002",
"REPO_CLIENT": "VTPLATFORM",
"SOURCE_PATH": "VTPLATFORM/VTQA-PACKAGES/IMPLE_WEB_P2_002/IMPLE_VTNET_OBJECTS",
"OUTPUT_PACKAGE": "VT-FULL-IMPLE_WEB_P2_002.tar.gz",
"COMPONENTE": "CORE"
}
Ejemplo conceptual para ejecución:
{
"PACKAGE_PATH": "VTPLATFORM/VTQA-PACKAGES/VT-FULL-IMPLE_WEB_P2_002.tar.gz",
"REPO_CLIENT": "VTPLATFORM",
"ENVIRONMENT": "VTQA",
"NODE": "NDA1",
"COMPONENTE": "CORE",
"TOKEN": "...",
"EXTRAFLAGS": ""
}
El mapeo exacto debe vivir en JenkinsAdapter.
Se realizará una POC para determinar qué puede extraerse desde Jenkins.
Objetivos de la POC:
Si la POC es exitosa:
Promotions obtiene opciones dinámicas desde Jenkins.
Si la POC no es suficiente:
Promotions usa metadata JSON configurable por cliente.
Arquitectura de fallback:
La convención de IMPLENAME queda pendiente de estandarización.
Recomendación:
VTFULL_*, para carpetas fuente.VT-FULL-<IMPLENAME>.tar.gz para el paquete generado.Opciones candidatas:
IMPLE_<CUSTOMER>_<BAP_NAME>_<VERSION>_<SEQ>
IMPLE_<BAP_NAME>_<VERSION>_<YYYYMMDD>_<SEQ>
IMPLE_<COMPONENT>_<VERSION>_<SEQ>
Ejemplo:
IMPLE_BANCA_PERSONAS_2_4_1_001
Decisión pendiente:
Definir convención oficial para IMPLENAME.
GET /promotions/implementations
POST /promotions/implementations
GET /promotions/implementations/{id}
GET /promotions/implementations/{id}/files
GET /promotions/implementations/{id}/steps2run
PUT /promotions/implementations/{id}/steps2run
POST /promotions/implementations/{id}/packages
GET /promotions/packages/{packageId}
GET /promotions/packages/{packageId}/download-link
POST /promotions/packages/{packageId}/deployments
GET /promotions/deployments/{deploymentId}
GET /promotions/deployments/{deploymentId}/events
GET /promotions/deployments/{deploymentId}/logs
GET /promotions/customers/{customerId}/config
PUT /promotions/customers/{customerId}/config
GET /promotions/events?entityType=deployment&entityId=<id>
GET /promotions/events?entityType=package&entityId=<id>
Validar:
promotion:create-folder.IMPLENAME no existe ya en el path destino..tar.gz y bapProps.steps2run.vtValidar:
promotion:edit-steps2run.<artifact>.tar.gz:<node|ALL>
IMPLE_VTNET_OBJECTS, salvo reglas especiales.Validar:
promotion:create-package.FOLDER_READY.steps2run.vt existe y es válido.bapProps existen en Nextcloud.Validar:
promotion:execute.PACKAGE_READY.packagePath resoluble.Promotions debe registrar logs propios para:
Métricas sugeridas:
implementations_created_total
package_generations_requested_total
package_generations_succeeded_total
package_generations_failed_total
deployments_requested_total
deployments_succeeded_total
deployments_failed_total
jenkins_poll_duration_ms
nextcloud_operation_duration_ms
bar_download_duration_ms
sse_connections_active
Cada flujo debe tener IDs correlacionables:
implementationId
packageId
deploymentId
generationBuildId
executionBuildId
auditEventId
Estado:
FAILED
Evento:
implementation.failed
Mensaje sugerido:
No se pudo obtener el BAP o bapProps desde BAR.
Estado:
FAILED
Posibles causas:
Estado:
PACKAGE_FAILED
Promotions debe guardar:
Estado:
DEPLOYMENT_FAILED
Promotions debe guardar:
Si la conexión SSE se corta, la UI debe poder reconectar y recuperar estado actual mediante REST.
Promotions usará Workspace para autenticar usuarios.
Promotions usará permisos granulares asociados a roles/grupos de Workspace.
Los secretos de integración deben estar fuera del frontend.
Promotions Backend debe manejar:
Si existe un token runtime requerido por Jenkins/vt-wizard, Promotions debe tratarlo como secreto.
Opciones:
Para v1 puede mantenerse como input no persistido si así lo exige el flujo actual.
| Riesgo | Impacto | Mitigación |
|---|---|---|
| Jenkins no expone parámetros dinámicos suficientes | UI no puede poblar nodos automáticamente | Fallback a metadata JSON por cliente. |
| Contrato de jobs nuevos no está definido | Promotions no puede invocar Jenkins establemente | Definir contrato explícito para generation/execution jobs. |
| Logs Jenkins no son parseables | No se puede mostrar resumen estructurado | Mostrar estado + logs + link; mejorar parser luego. |
| Nextcloud falla al crear carpetas | Implementaciones quedan incompletas | Operaciones idempotentes y validación previa de paths. |
IMPLENAME no estandarizado |
Colisiones o nombres ambiguos | Definir convención antes de producción. |
| Permisos demasiado amplios | Riesgo operativo | Usar permisos granulares en Workspace. |
| Cliente on-premise intenta ejecutar remoto | Error operativo | Ocultar acción si supportsRemoteExecution=false. |
| Link directo Nextcloud no cumple necesidades de seguridad futuras | Exposición o trazabilidad limitada | Evolucionar a link efímero/proxy. |
| Jenkins abort/cancel no está claro | No se puede cancelar ejecución | Dejar cancel fuera de v1. |
bapProps.steps2run.vt.steps2run.vt.Generate VT-FULL Job.Execute VT-FULL Job.supportsRemoteExecution.IMPLENAME.vt-wizard.Decisión: Promotions invoca Jenkins, pero no modela internamente sus stages.
Motivo: Reducir alcance y aprovechar el flujo existente.
Consecuencia: La arquitectura depende de Jenkins Adapter y del contrato de parámetros.
Decisión: Promotions usará dos jobs/scripts: uno para generar VT-FULL y otro para ejecutar un VT-FULL ya generado.
Motivo: Evitar regenerar paquetes durante ejecución y mejorar trazabilidad.
Consecuencia: Se requiere definir dos contratos de parámetros con Jenkins.
Decisión: Promotions obtiene BAP y bapProps desde BAR.
Motivo: Centralizar origen de artefactos y evitar carga manual.
Consecuencia: Se requiere BAR Adapter y manejo de errores de descarga.
Decisión: El BAP .tar.gz y bapProps se copian dentro de IMPLE_VTNET_OBJECTS.
Motivo: Mantener compatibilidad con el pipeline actual.
Consecuencia: Nextcloud sigue siendo el storage operativo para Jenkins.
steps2run.vt será editable en v1Decisión: La UI permitirá editar únicamente steps2run.vt.
Motivo: Reducir riesgo operativo y evitar carga/modificación arbitraria de artefactos.
Consecuencia: Props y BAP quedan controlados por BAR y por el sistema.
Decisión: Promotions hará polling a Jenkins y emitirá SSE a la UI.
Motivo: Evitar que la UI dependa directamente de Jenkins.
Consecuencia: Promotions debe mantener workers/pollers o procesos de seguimiento.
Decisión: La metadata faltante de cliente vivirá en JSON configurable en DB.
Motivo: No toda la metadata existe hoy en un sistema central.
Consecuencia: Se requiere UI/API administrativa y validación de schema.
Decisión: v1 puede devolver un link directo a Nextcloud para descargar el paquete.
Motivo: Reducir complejidad inicial.
Consecuencia: Links efímeros quedan como mejora futura.
IMPLENAME?Generate VT-FULL Job?Execute VT-FULL Job?PACKAGE_PATH, PACKAGE_NAME + REPO_CLIENT, packageId, u otro contrato?VTPROD?steps2run.vt?steps2run.vt al momento de empaquetar?| Término | Significado |
|---|---|
| Promotions | Sistema/capacidad para orquestar implementaciones desde Workspace. |
| Workspace | Componente Veritran donde vivirá Promotions y desde donde se gestionará autenticación/autorización. |
| BAR | BAP Application Repository, origen del BAP y bapProps. |
| BAP | Artefacto de aplicación/implementación devuelto por BAR como .tar.gz. |
bapProps |
Archivo de propiedades asociado al BAP. |
| Nextcloud | Storage actual en repo.veritran.com. |
repo.veritran.com |
Repositorio hospedado por Veritran. |
VTQA-PACKAGES |
Carpeta de trabajo inicial para implementaciones VTQA. |
VTPROD-PACKAGES |
Carpeta de producción, fuera de alcance v1. |
IMPLENAME |
Nombre de implementación usado como folder y parte del nombre del paquete final. |
IMPLE_VTNET_OBJECTS |
Carpeta donde se colocan BAP, bapProps y steps2run.vt. |
steps2run.vt |
Receta editable que mapea artefactos contra nodos. |
VT-FULL-<IMPLENAME>.tar.gz |
Paquete final generado por Jenkins. |
| Generate VT-FULL Job | Job/script Jenkins dedicado a generar el paquete final. |
| Execute VT-FULL Job | Job/script Jenkins dedicado a ejecutar un paquete final ya generado. |
| Jenkins Adapter | Capa que encapsula invocación y consulta de Jenkins. |
| Nextcloud Adapter | Capa que encapsula operaciones contra Nextcloud. |
| BAR Adapter | Capa que encapsula consumo de BAR. |
| SSE | Server-Sent Events para progreso casi realtime. |
| Cliente conectado | Cliente donde Veritran puede ejecutar remotamente. |
| Cliente on-premise | Cliente donde la ejecución se realiza manualmente dentro de infraestructura del cliente. |
Promotions v1 debe actuar como una capa de producto y orquestación sobre el flujo existente de implementaciones VTQA.
La solución propuesta automatiza la preparación manual en Nextcloud, integra BAR como fuente de BAP, mantiene Jenkins como caja negra, permite generar paquetes finales, habilita ejecución remota solo cuando corresponde, ofrece progreso casi realtime vía SSE y conserva el contrato portable VT-FULL-<IMPLENAME>.tar.gz.
La actualización más relevante es que el flujo de Jenkins se desacoplará en dos jobs/scripts específicos para Promotions:
Generate VT-FULL Job
Execute VT-FULL Job
Esto evita regenerar paquetes durante la ejecución y permite que cada deployment quede asociado a un package concreto y trazable.
El diseño favorece evolución incremental: primero encapsular y gobernar el flujo actual; luego mejorar seguridad, auditoría, descargas, parsing de resultados, VTPROD, runners customer-hosted o incluso reemplazo gradual de Jenkins.