idtt.
ESTUDIO · TEC

CI/CD cuando la IA escribe el código

Si el código se genera más rápido de lo que se entiende, el pipeline pasa a ser el revisor principal. Siete capas pensadas para transporte y medios de pago, con el código de cada una.

21 Sep 2026
Jorge Sandoval — Head of Technology
Financiero
Desarrollo
10 min
/ EL SUPUESTO

El code review asume que el autor entendió el diff.

Todo proceso de code review descansa en un contrato implícito: el autor entendió lo que escribió, y el revisor busca lo que al autor se le escapó.

Con código generado por IA, ese contrato se debilita. Se puede iterar cinco veces con un asistente, quedarse con la versión que pasó la prueba manual y abrir un MR sin haber trazado las consecuencias del diff final. No es negligencia: la velocidad de generación se separó de la velocidad de comprensión.

El pipeline tiene que compensar lo que el autor no verificó.

/ ALTA VOLUMETRÍA

Dos perfiles, dos maneras de fallar.

Un cambio que pasa lint, tipos y pruebas E2E puede romper cualquiera de las dos, y solo con carga o con reintentos reales.

SAAS TAXI · MOVILIDAD

~300 TPS

Sostenido, todos los días

1,200 conductores activos reportando ubicación cada 3 a 5 segundos. No es un pico: es carga continua.

PLATAFORMA DE MEDIOS DE PAGO

+67%

En el pico de demanda

De 15 a 25 TPS en fechas de alta demanda. Cada transacción es dinero real: cero tolerancia a pérdida de datos.

LO QUE NO VEN LINT, TIPOS NI E2E

  • Un cobro duplicado por un reintento
  • Un timeout ausente que se vuelve caída en cascada
  • Una consulta sin límite que se cae con millones de filas
/ EL GATE

Siete capas, ordenadas por costo.

Lo barato falla primero y lo caro corre solo donde el riesgo lo justifica. Todos los jobs corren en pipelines de merge request.

CAPA 0 · FAST

Integridad del gate

Detecta supresiones, tests saltados y umbrales bajados. Un ratchet compara con la rama base: el número solo puede bajar.

Ratchet en CI
CAPA 1 · FAST

Forma

Imports alucinados, dead code y deriva de estilo. Con --max-warnings 0, cada advertencia cuenta como fallo.

ESLint · Biome
CAPA 2 · FAST

Tipos

Un any de escape y contratos que cambian en silencio, exigidos en modo estricto solo sobre lo que cambió el MR.

tsc --strict
CAPA 3 · VERIFY

Seguridad

Secretos en el rango del MR, patrones inseguros y dependencias de producción, con reglas propias para pagos.

Gitleaks · Semgrep · pnpm audit
CAPA 4 · VERIFY

Comportamiento

Regresiones, idempotencia bajo concurrencia y tests que nunca podrían fallar.

Jest · Stryker
CAPA 5 · LOAD

Carga y resiliencia

Degradación a escala con el perfil real de la plataforma. Si p95 o los errores cruzan el umbral, el MR no avanza.

k6
CAPA 6 · SHIP

Entrega

Lo que se escapó de todo lo anterior: canary con análisis contra el SLO y rollback automático.

Argo Rollouts

Ningún gate llega a cero. Lo que separa a un equipo con experiencia de uno sin ella no es evitar el 100% de los incidentes, sino cuánto tarda en detectarlos y en recuperarse.

La pregunta que importa es cómo se recupera.

/ EL CÓDIGO

Capa por capa, con su código

Configuración lista para copiar. Los umbrales son de ejemplo: se fijan contra el SLO real de cada plataforma.

0

El gate también se protege

CAPA 0 · FAST

Un agente que persigue el check en verde puede llegar por el camino corto: un @ts-ignore, un it.skip, un umbral más bajo. El ratchet compara con la rama base y solo deja bajar el conteo. El pipeline vive en un proyecto central con versión fija.

RATCHET · .gitlab-ci.yml
ratchet:
  stage: fast
  variables:
    GIT_DEPTH: "0"
  script:
    - |
      B=$CI_MERGE_REQUEST_DIFF_BASE_SHA
      S='eslint-disable|@ts-ignore|(it|test)\.skip'
      n() { git grep -hE "$S" $1 -- src test | wc -l; }
      test $(n HEAD) -le $(n $B)
1·2

Tipos estrictos solo sobre lo que cambió

CAPAS 1 Y 2 · FAST

Pasar archivos a tsc por línea de comandos ignora el tsconfig y da falsos errores en alias como @/ (lo probamos). Se genera un tsconfig temporal que extiende el base e incluye solo los archivos del MR.

TSC · SOLO LO QUE CAMBIÓ
strict-changed:
  stage: fast
  variables:
    GIT_DEPTH: "0"
  rules:
    - changes: ["**/*.ts"]
  script:
    - |
      git diff --name-only --diff-filter=d \
        $CI_MERGE_REQUEST_DIFF_BASE_SHA HEAD -- '*.ts' \
        | jq -R . | jq -s '{extends:"./tsconfig.json",
          compilerOptions:{strict:true},include:.}' \
        > tsconfig.pr.json
      pnpm exec tsc -p tsconfig.pr.json --noEmit
3

Seguridad, con una regla propia para pagos

CAPA 3 · VERIFY

Gitleaks escanea el rango de commits del MR y detecta un token agregado y borrado en el commit siguiente. Semgrep suma reglas propias, como exigir Idempotency-Key en todo endpoint de cobro.

SEMGREP · rules/idempotency.yml
rules:
  - id: charge-endpoint-without-idempotency-key
    languages: [typescript]
    severity: ERROR
    message: Los endpoints de cobro deben leer el header Idempotency-Key
    paths:
      include:
        - "**/src/payments/**"
    patterns:
      - pattern: |
          @Post(...)
          async $FN(...) { ... }
      - pattern-not: |
          @Post(...)
          async $FN(..., @Headers('idempotency-key') $K, ...) { ... }
4

Idempotencia bajo concurrencia

CAPA 4 · VERIFY

Una idempotencia ingenua revisa si existe y luego inserta: con un request funciona, con tres reintentos simultáneos cobra tres veces. En un ejemplo mínimo que probamos, un test sin aserciones dio 100% de cobertura y 20% de mutation score.

JEST · charges.idempotency.spec.ts
import { randomUUID } from 'crypto';

it('mismo Idempotency-Key: un solo cargo', async () => {
  const key = randomUUID();
  const send = () =>
    api
      .post('/charges')
      .set('Idempotency-Key', key)
      .send({ amount: 1500, currency: 'PEN' });

  const res = await Promise.all([send(), send(), send()]);

  expect(new Set(res.map((r) => r.body.id)).size).toBe(1);
  expect(await ledger.countByKey(key)).toBe(1);
});
5

Carga con el perfil real

CAPA 5 · LOAD

1,200 conductores que reportan cada 4 segundos son ~300 requests por segundo. Cuando p95 o la tasa de error cruzan el umbral, k6 termina con código 99 y el MR no avanza. Umbrales de ejemplo.

K6 · load/tracking.js
import http from 'k6/http';
import { sleep } from 'k6';

export const options = {
  vus: 1200,
  duration: '5m',
  thresholds: {
    http_req_failed: ['rate<0.001'],
    http_req_duration: ['p(95)<250'],
  },
};

const body = JSON.stringify({ lat: -6.77, lng: -79.84 });

export default () => {
  http.post(`${__ENV.API}/tracking`, body);
  sleep(4);
};
6

Entrega progresiva y rollback por SLO

CAPA 6 · SHIP

El cambio recibe un porcentaje pequeño del tráfico, se mide contra el SLO y, si se degrada, se revierte solo. Ningún gate llega a cero: importa cuánto tarda la recuperación.

ARGO ROLLOUTS · rollout.yaml
strategy:
  canary:
    steps:
      - setWeight: 5
      - pause: { duration: 5m }
      - analysis:
          templates:
            - templateName: slo-p95
      - setWeight: 50
      - pause: { duration: 10m }

Más notas del equipo

/ QUIERO ALGO SIMILAR

Tu próximo proyectoempieza hoy.

Cuéntanos qué necesitas — te decimos cómo lo resolvemos, sin vueltas ni letra chica.

Hablemos

O escríbenos directo → hola@identity.pe