Hay una pregunta que este curso no ha hecho todavía y conviene hacerla sin rodeos: ¿dónde está el código de MercadoFresco? La respuesta honesta es que está en el portátil de Luis, en ~/proyectos/tienda, con un git init de hace catorce meses y 1.847 commits que nadie más ha visto. Hay una copia comprimida en un USB de septiembre. Marta tiene una carpeta tienda-v2-FINAL que no sabe si es anterior o posterior. Y cuando Luis despliega, hace scp de la carpeta entera a una instancia del ASG asg-mercadofresco-tienda y reinicia el servicio a mano.

Todo lo construido en siete módulos descansa sobre un código fuente que vive en un solo disco duro sin copia fiable. Este módulo arregla eso y empieza por el principio: dónde vive el código y cómo se gobiernan los cambios.

Aviso de coste. CodeCommit cobra 1 USD por usuario activo y mes a partir del sexto, con 50 GB y 10.000 peticiones Git incluidas: para el equipo de tres de MercadoFresco, cero. Las conexiones de CodeConnections hacia GitHub o GitLab también son gratuitas; se paga el uso aguas abajo, que veremos en 08-02 y 08-04. Todos los datos y credenciales de esta lección son ficticios.

Contenido

  1. El punto de partida: un repositorio en un portátil
  2. Qué aporta un repositorio Git gestionado
  3. La situación de CodeCommit, sin adornos
  4. Comparativa: CodeCommit, GitHub, GitLab y Bitbucket
  5. CodeConnections: el puente entre AWS y tu proveedor Git
  6. Los repositorios de MercadoFresco
  7. Autenticación: HTTPS, SSH, el ayudante de la CLI y roles
  8. Políticas IAM: quién puede empujar a main
  9. Estrategia de ramas: trunk-based frente a GitFlow
  10. El flujo de ramas de MercadoFresco
  11. Pull requests: revisión, aprobación y fusión
  12. Reglas de aprobación y protección de main
  13. Convenciones: commits, versión semántica y etiquetas
  14. El .gitignore y por qué nunca se sube un .env
  15. Notificaciones y disparadores
  16. Migrar el repositorio de Luis
  17. Coste y limpieza
  18. Errores comunes y consejos
  19. Ejercicios
  20. Conclusión

El punto de partida: un repositorio en un portátil

Marta hizo un inventario del riesgo antes de tocar nada:

Riesgo Situación actual Consecuencia
Pérdida del portátil Copia USB de hace 5 meses 5 meses de trabajo perdidos
Dos personas tocan lo mismo Se avisan por chat Sobrescritura silenciosa
Saber qué hay en producción «Lo que subí el viernes» Nadie reproduce un fallo
Volver a la versión anterior Buscar en la papelera Minutos u horas de caída
Revisar antes de que entre No se hace Errores vistos en producción
Secretos en el código Hay un .env con la clave de RDS Fuga al compartir el repositorio

Los cuatro primeros los resuelve cualquier repositorio remoto. Los tres últimos necesitan además gobierno: reglas sobre quién puede hacer qué y en qué condiciones entra un cambio. Esa distinción es la lección entera: alojar el código es la parte fácil.

Qué aporta un repositorio Git gestionado

Git es distribuido y nada obliga a tener servidor, pero todo equipo designa una copia como la de referencia, y esa copia necesita estar disponible, tener copia de seguridad y controlar quién escribe.

Aspecto Servidor propio (EC2 + Gitea/GitLab CE) Gestionado
Durabilidad La del EBS; tú haces las copias Replicado entre AZ, sin acción tuya
Disponibilidad Una instancia = un punto único de fallo Acuerdo de servicio del proveedor
Cifrado en reposo Lo configuras tú Automático; en CodeCommit, con KMS
Identidad Usuarios de sistema o LDAP propio IAM (CodeCommit) u OIDC/SAML
Parcheado y CVE Tuyos Del proveedor
Coste real Instancia + EBS + tu tiempo Por usuario o incluido
Auditoría Logs del servidor, si los guardas CloudTrail o registro del proveedor

Para tres personas, mantener un servidor Git es tiempo que no va al producto: MercadoFresco no lo monta. Merece un párrafo el cifrado en reposo de CodeCommit, que conecta con el módulo 4: cifra siempre, sin opción de desactivarlo, y puede hacerlo con una clave del cliente. Aquí eso significa reutilizar alias/mercadofresco-datos, la misma clave que ya cifra Aurora y los buckets: una clave, una política, una rotación y un único sitio donde revocar el acceso. Es la ventaja concreta de la integración: no hay un segundo sistema de cifrado que gobernar.

La situación de CodeCommit, sin adornos

Desde julio de 2024, AWS no admite nuevos clientes en CodeCommit. Si tu cuenta ya lo usaba antes, sigue funcionando con soporte y correcciones de seguridad. Si es nueva, la consola no te dejará crear repositorios. No hay fecha de cierre anunciada, pero tampoco funcionalidades nuevas: la recomendación oficial para proyectos nuevos es un proveedor Git externo conectado con CodeConnections.

Tres consecuencias prácticas: te lo vas a encontrar en empresas que lo adoptaron entre 2015 y 2024 con cientos de repositorios, así que saber operarlo es útil; no lo elijas para un proyecto nuevo, ni aunque tu cuenta antigua te deje, porque estarías construyendo sobre un servicio sin evolución; y lo que importa aquí no es el servicio, son los conceptos —ramas, pull requests, reglas de aprobación, protección de rama, convenciones de commit y de versión son idénticos en GitHub, GitLab o Bitbucket, y son lo que consumirán 08-02, 08-03 y 08-04.

Marta toma la decisión que tomarías hoy: el código va a GitHub, y AWS se conecta con CodeConnections. La lección sigue explicando CodeCommit porque te lo encontrarás y porque su modelo de permisos —IAM puro— es didácticamente muy claro.

Comparativa: CodeCommit, GitHub, GitLab y Bitbucket

Criterio CodeCommit GitHub GitLab Bitbucket
Nuevos clientes No admitidos
Identidad IAM nativo Propia; SSO de pago Propia; SSO Atlassian
Revisión de código Funcional pero pobre La mejor del mercado Muy completa Correcta
CI/CD propio No (usa CodeBuild) GitHub Actions GitLab CI Bitbucket Pipelines
Integración AWS Nativa total CodeConnections + OIDC CodeConnections + OIDC CodeConnections
Autoalojable No Enterprise Server Sí, self-managed Data Center
Ecosistema Nulo Enorme Amplio e integrado Ligado a Jira
Residencia del código Tu cuenta y región AWS Servidores de GitHub GitLab o tu servidor Atlassian
Coste 3 / 30 personas 0 / ~25 USD mes 0 / ~120 USD mes 0 / ~870 USD mes 0 / ~90 USD mes

Criterio de elección, en el orden en que hay que aplicarlo:

  1. ¿Hay exigencia legal o contractual de que el código no salga de tu cuenta o de la UE? Si es innegociable, tus opciones son CodeCommit (solo si ya lo tenías) o GitLab autoalojado. Es el único criterio que anula a todos los demás.
  2. ¿Dónde está ya tu equipo? Mover a gente que lleva años en GitHub tiene un coste de fricción diario que se paga para siempre. Este criterio decide la mayoría de los casos.
  3. ¿Usarás el CI/CD del proveedor o el de AWS? Actions y GitLab CI son excelentes y despliegan en AWS con OIDC sin claves; si prefieres construcción y despliegue dentro de AWS —con sus roles, sus VPC y su factura—, CodeConnections da lo mejor de ambos.
  4. ¿Cuánto ecosistema necesitas? En escaneo de dependencias, incidencias y tableros, GitHub y GitLab ganan sin discusión.

MercadoFresco: el equipo ya está en GitHub, no hay restricción sobre el código fuente (los datos de clientes sí están restringidos a eu-west-1, pero eso es otra cosa) y quieren construcción y despliegue en AWS para reutilizar roles y alarmas. GitHub + CodeConnections + CodeBuild/CodeDeploy/CodePipeline.

CodeConnections: el puente entre AWS y tu proveedor Git

CodeConnections —hasta 2023, AWS CodeStar Connections; verás ambos nombres en la documentación y en ARN antiguos— es un recurso de AWS que representa una autorización OAuth entre tu cuenta y una organización de GitHub, GitLab o Bitbucket. Una vez creada, CodePipeline o CodeBuild leen el repositorio y reciben sus eventos sin que tú almacenes ningún token en ningún sitio.

Se crea en dos pasos, y el segundo es obligatoriamente manual:

# Paso 1: crear la conexion. Nace en PENDING y todavia no sirve para nada.
aws codeconnections create-connection \
  --provider-type GitHub \
  --connection-name conn-mercadofresco-github \
  --tags Key=Proyecto,Value=mercadofresco Key=Entorno,Value=produccion \
         Key=Componente,Value=cicd Key=Propietario,Value=marta Key=CentroCoste,Value=plataforma \
  --profile mercadofresco-dev --region eu-west-1

Paso 2, en la consola y solo en la consola. Developer Tools → Settings → Connections, seleccionar la conexión pendiente y pulsar «Update pending connection». Se abre el flujo OAuth de GitHub, se instala la app AWS Connector for GitHub en la organización y se eligen los repositorios autorizados. Este paso no se puede automatizar por diseño: es una autorización humana en un proveedor externo, no una llamada a la API de AWS. Si una plantilla de CloudFormation «crea conexiones listas», crea el recurso y alguien lo autoriza a mano una vez.

Comprueba con aws codeconnections list-connections que el estado es AVAILABLE antes de seguir: una conexión en PENDING es la causa número uno de que la acción de origen de un pipeline falle con un error de permisos poco descriptivo, y volveremos a ello en 08-04.

Concede acceso solo a los repositorios necesarios. GitHub ofrece dar acceso a toda la organización; elige «Only select repositories» y marca los dos de MercadoFresco. Es el privilegio mínimo de 04-01 aplicado fuera de AWS.

Los repositorios de MercadoFresco

Repositorio Contenido Quién lo toca Cadencia
mercadofresco-tienda Web, API, consumidores de colas, código de las Lambdas, pruebas, buildspec.yml, appspec.yml Luis Varias veces al día
mercadofresco-infra Definición de la infraestructura, scripts de operación, runbooks Marta Semanal

Separarlos permite desplegar la aplicación veinte veces al día sin arrastrar cambios de infraestructura y someter un cambio de red a revisión más estricta. El contenido de mercadofresco-infra lo llenaremos en el módulo 9: hoy solo existe el repositorio.

# En CodeCommit
aws codecommit create-repository \
  --repository-name mercadofresco-tienda \
  --repository-description "Aplicacion web, API y consumidores de MercadoFresco" \
  --kms-key-id alias/mercadofresco-datos \
  --tags Proyecto=mercadofresco,Entorno=produccion,Componente=codigo,Propietario=luis,CentroCoste=plataforma \
  --profile mercadofresco-dev --region eu-west-1

# En GitHub (la opcion elegida)
gh repo create mercadofresco/mercadofresco-tienda --private
gh repo create mercadofresco/mercadofresco-infra --private

--kms-key-id es donde se materializa el cifrado con clave propia: si algún día revocas esa clave, el repositorio deja de ser legible, que es exactamente lo que quieres ante una fuga. Y el repositorio es siempre privado, sin «de momento público que ya lo cambiaremos»: los rastreadores que buscan credenciales en repositorios públicos tardan minutos, no días.

Autenticación: HTTPS, SSH, el ayudante de la CLI y roles

Aquí está la diferencia conceptual más interesante de CodeCommit: no hay usuarios de Git, hay identidades de IAM. No existe un «usuario luis» del servidor con su contraseña; existe su usuario o rol de IAM, y el permiso para empujar es una política. Dar de baja a alguien es quitarle IAM, no acordarse de un segundo sistema.

Método Cómo funciona Cuándo Inconveniente
Credenciales HTTPS de IAM Usuario/contraseña generados en IAM Portátil con usuario IAM Material de larga duración
Claves SSH Clave pública en el usuario IAM Portátil, si prefieres SSH Igual
Ayudante de credenciales Git pide a la CLI una firma SigV4 Recomendado: SSO, MFA, roles Requiere la CLI
Rol IAM La máquina asume un rol, sin credenciales CodeBuild, EC2, Lambda Solo para máquinas

Usa el ayudante de credenciales. Su virtud es que no guarda nada: cada vez que Git necesita autenticarse invoca aws codecommit credential-helper, que firma con las credenciales actuales del perfil. Si vienen de IAM Identity Center y caducan a las ocho horas, el acceso caduca con ellas. Es el principio de credenciales temporales de 04-01 aplicado al repositorio.

git config --global credential.helper '!aws --profile mercadofresco-dev codecommit credential-helper $@'
git config --global credential.UseHttpPath true

git clone https://git-codecommit.eu-west-1.amazonaws.com/v1/repos/mercadofresco-tienda

credential.UseHttpPath true es obligatorio y se olvida constantemente: sin él, Git no envía la ruta del repositorio, la firma se calcula sobre un recurso equivocado y obtienes un 403 que no explica nada.

En GitHub, el equivalente moderno para las máquinas es OIDC, no una clave de acceso. En lugar de guardar un AWS_ACCESS_KEY_ID como secreto del repositorio, se establece confianza entre el proveedor OIDC de GitHub y un rol de tu cuenta, con una condición sobre la organización y el repositorio concretos:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::111122223333:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringEquals": { "token.actions.githubusercontent.com:aud": "sts.amazonaws.com" },
      "StringLike": { "token.actions.githubusercontent.com:sub": "repo:mercadofresco/mercadofresco-tienda:*" }
    }
  }]
}

La condición sub impide que cualquier repositorio del mundo asuma tu rol. Un StringLike con repo:* es un agujero de seguridad grave que se ha visto en producción más de una vez.

Políticas IAM: quién puede empujar a main

En CodeCommit la protección de rama se expresa como política de IAM, mediante la condición codecommit:References. Esta política deniega empujar directamente a main:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenegarEscrituraDirectaEnMain",
    "Effect": "Deny",
    "Action": ["codecommit:GitPush", "codecommit:DeleteBranch",
               "codecommit:PutFile", "codecommit:DeleteFile"],
    "Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
    "Condition": {
      "StringEqualsIfExists": { "codecommit:References": ["refs/heads/main"] },
      "Null": { "codecommit:References": "false" }
    }
  }]
}

Tres detalles que se copian mal a menudo. StringEqualsIfExists limita la denegación a lo que afecta a refs/heads/main: un push a funcionalidad/franja-horaria no encaja y se permite. Null: {"codecommit:References": "false"} significa «aplica solo si la clave está presente»; sin ella denegarías también operaciones sin referencia de rama, bloqueando cosas que no querías. Y PutFile y DeleteFile cierran la puerta de la consola web, que permite editar sin pasar por Git y es el agujero que más se olvida —en cambio, no deniegues las acciones MergeBranchesBy*: son las que ejecuta la fusión de una PR, y bloquearlas impide cerrar incluso las PR aprobadas.

Deny gana siempre sobre Allow, como vimos en 04-01: esto es efectivo aunque Luis tenga adjunta AWSCodeCommitPowerUser. En GitHub el mecanismo cambia —reglas de protección de rama o rulesets— pero la intención es idéntica.

Estrategia de ramas: trunk-based frente a GitFlow

Aspecto Trunk-based GitFlow
Ramas de larga vida Una: main main + develop + release/* + hotfix/*
Vida de una rama de trabajo Horas o 1-2 días Días o semanas
Conflictos de fusión Pequeños y frecuentes Grandes y dolorosos
Complejidad mental Baja Alta; hay que documentarla
Entrega continua Es su premisa Mal: la rama de release estorba
Funcionalidad a medias Se oculta con banderas Se aísla en su rama
Versiones simultáneas Difícil Es lo que resuelve bien

GitFlow se diseñó en 2010 para software que se publica en versiones: un instalador, una biblioteca, un producto que el cliente actualiza cuando quiere. Ahí tiene sentido mantener la 1.4 mientras desarrollas la 1.5. Su propio autor publicó después una nota diciendo que si entregas de forma continua —una web, un SaaS— probablemente no lo necesites.

MercadoFresco es una tienda web: solo existe una versión, la que está en producción ahora. Una rama release/* no aporta nada y una develop que vive semanas garantiza fusiones dolorosas entre tres personas que tocan los mismos ficheros.

Decisión de Marta: trunk-based con matices. main siempre desplegable, ramas de funcionalidad cortas, y una rama desarrollo que existe por una razón temporal: hasta que el pipeline de 08-04 esté maduro y las pruebas den confianza, quiere un punto donde los cambios convivan un día antes de llegar a main. Es un compromiso consciente con fecha de caducidad declarada: un andamio, no una catedral.

El flujo de ramas de MercadoFresco

gitGraph
    commit id: "v1.4.0"
    branch desarrollo
    checkout desarrollo
    commit id: "sync"
    branch funcionalidad/franja-horaria
    checkout funcionalidad/franja-horaria
    commit id: "modelo pedido"
    commit id: "API + pruebas"
    checkout desarrollo
    merge funcionalidad/franja-horaria id: "PR 42"
    commit id: "humo dev OK"
    checkout main
    merge desarrollo tag: "v1.5.0"
    branch correccion/iva-canarias
    checkout correccion/iva-canarias
    commit id: "fix IVA"
    checkout main
    merge correccion/iva-canarias tag: "v1.5.1"
    checkout desarrollo
    merge main id: "sync fix"

Las reglas escritas, que viven en el CONTRIBUTING.md:

Rama Origen Destino Vida Quién fusiona
main Permanente Nadie directamente
desarrollo main main vía PR Permanente Marta, tras revisión
funcionalidad/<n> desarrollo desarrollo vía PR Máx. 3 días Autor, 1 aprobación
correccion/<n> main main y desarrollo Horas Autor, 1 aprobación
experimento/<n> Cualquiera Se borra Días No se fusiona

El límite de tres días no es burocracia: una rama que vive dos semanas diverge tanto que fusionarla deja de ser una fusión y pasa a ser una reintegración; si un cambio no cabe en tres días, se parte. Las correcciones urgentes salen de main, no de desarrollo, que arrastraría cambios a medias a producción; y hay que fusionarlas también hacia desarrollo, o el fallo reaparecerá en la siguiente entrega. Y las ramas se borran al fusionar: un repositorio con 140 ramas muertas es un repositorio donde nadie encuentra nada.

Pull requests: revisión, aprobación y fusión

Un pull request es la propuesta de fusionar una rama en otra, con un espacio para discutirla antes de que ocurra. Es lo que convierte «Luis subió algo» en «el equipo aceptó un cambio».

aws codecommit create-pull-request \
  --title "Anadir franja horaria de entrega al pedido" \
  --description "$(cat <<'EOF'
## Que cambia
Anade el campo `franja_entrega` (manana|tarde|24h) al pedido y lo propaga al evento
`PedidoConfirmado` como atributo opcional.

## Por que
Peticion de Sara: el 31 % de las incidencias de reparto son por ausencia del cliente.

## Como probarlo
1. POST /pedidos con "franja_entrega": "tarde"
2. Comprobar el mensaje en cola-mercadofresco-pedidos
3. Comprobar que un pedido SIN el campo sigue funcionando (compatibilidad hacia atras)

## Riesgos
Cambio de esquema en Aurora: columna NULLABLE, sin valor por defecto, sin indice nuevo.
Consumidores antiguos ignoran el atributo. Ver 08-05.
EOF
)" \
  --targets repositoryName=mercadofresco-tienda,sourceReference=funcionalidad/franja-horaria,destinationReference=desarrollo \
  --profile mercadofresco-dev --region eu-west-1

Esa descripción es una plantilla real y merece copiarse. Una PR sin descripción obliga al revisor a reconstruir la intención leyendo el diff, la forma más lenta y menos fiable de entender un cambio. Las cuatro secciones se responden en tres minutos y ahorran veinte al revisor.

Los comentarios pueden ser generales o anclarse a una línea concreta del diff con post-comment-for-pull-request y su parámetro --location filePath=app/pedidos.py,filePosition=147, que es donde ocurre la revisión útil: sobre la línea, no en un correo aparte.

Las tres estrategias de fusión, idénticas en cualquier plataforma:

Estrategia Qué hace Historia resultante Cuándo
Fast-forward Mueve el puntero, sin commit nuevo Lineal, conserva todos los commits Rama de 1 commit ya rebasada
Squash Aplasta la rama en un commit Lineal y limpia: 1 PR = 1 commit Por defecto en funcionalidades
Three-way Commit de fusión con dos padres Ramificada, conserva el detalle Entre ramas de larga vida

MercadoFresco usa squash para las ramas de funcionalidad y three-way para desarrollomain. El razonamiento: los quince commits de «wip», «arreglo el test» y «ahora sí» no aportan nada dentro de seis meses, y un git log de main donde cada línea es un cambio completo y descrito es una herramienta de diagnóstico excelente durante un incidente; en cambio, al pasar desarrollo a main interesa conservar los commits individuales para poder revertir uno solo. Una consecuencia del squash que sorprende la primera vez: el commit resultante es nuevo y no comparte identificador con los de la rama, así que la rama queda «no fusionada» a ojos de Git aunque su contenido esté dentro. Bórrala igualmente.

aws codecommit merge-pull-request-by-squash \
  --pull-request-id 42 --repository-name mercadofresco-tienda \
  --commit-message "Anadir franja horaria de entrega al pedido (#42)" \
  --profile mercadofresco-dev --region eu-west-1

Reglas de aprobación y protección de main

Una PR que cualquiera fusiona sin que nadie la mire es un formalismo. Lo que la convierte en un control real es una plantilla de regla de aprobación: una política que se aplica automáticamente a las PR que cumplan un criterio y exige aprobaciones de un conjunto de personas.

aws codecommit create-approval-rule-template \
  --approval-rule-template-name plantilla-mf-main-requiere-aprobacion \
  --approval-rule-template-description "Toda PR hacia main necesita 1 aprobacion del equipo" \
  --approval-rule-template-content '{
    "Version": "2018-11-08",
    "DestinationReferences": ["refs/heads/main"],
    "Statements": [{
      "Type": "Approvers",
      "NumberOfApprovalsNeeded": 1,
      "ApprovalPoolMembers": ["arn:aws:sts::111122223333:assumed-role/rol-mercadofresco-desarrollo/*"]
    }]
  }' \
  --profile mercadofresco-dev --region eu-west-1

# La plantilla no hace nada hasta que se asocia a un repositorio
aws codecommit associate-approval-rule-template-with-repository \
  --approval-rule-template-name plantilla-mf-main-requiere-aprobacion \
  --repository-name mercadofresco-tienda \
  --profile mercadofresco-dev --region eu-west-1

DestinationReferences limita la regla a las PR hacia main: agilidad en el día a día, rigor en la puerta de producción. ApprovalPoolMembers admite el patrón assumed-role/<rol>/*, que es el correcto cuando el equipo entra con SSO y las identidades son sesiones de rol, no usuarios IAM. NumberOfApprovalsNeeded: 1 es lo razonable para tres personas; con dos, el proceso se bloquearía en cuanto alguien se fuera de vacaciones. Y el autor no puede aprobar su propia PR: CodeCommit lo impide de forma nativa y en GitHub hay que activarlo, pero es la mitad del valor del mecanismo.

La segunda condición —que la construcción esté en verde— no se puede expresar aquí, porque estas reglas solo cuentan aprobaciones humanas. Hay dos caminos: que CodeBuild apruebe la PR programáticamente al terminar bien, o que la comprobación viva en el proveedor Git como required status check. Esta pieza se cierra en 08-04, cuando el pipeline exista y tenga algo que decir sobre la calidad del cambio.

Convenciones: commits, versión semántica y etiquetas

Las convenciones parecen cosmética hasta el día en que un pipeline las lee y decide con ellas.

El formato es tipo(ámbito): descripción en imperativofeat(pedidos): anadir franja horaria de entrega, fix(pagos): evitar doble cobro cuando la pasarela responde 504, chore(deps): subir boto3:

Tipo Significado Efecto en la versión
feat Funcionalidad nueva MINOR
fix, perf Corrección o mejora PATCH
refactor, docs, test, chore Sin cambio funcional Ninguno
Cualquiera con BREAKING CHANGE: en el cuerpo Cambio incompatible MAJOR

Versionado semántico (MAJOR.MINOR.PATCH) es el otro lado de la misma moneda: v1.5.0 frente a v1.4.3 dice qué esperar. En una tienda donde el único cliente es tu frontal, el MAJOR importa poco; en cuanto publicas una API que consume el ERP del almacén, importa mucho.

git tag -a v1.5.0 -m "Franja horaria de entrega + cache de catalogo"
git push origin v1.5.0
git log --oneline v1.4.3..v1.5.0

El valor operativo es que el artefacto desplegado lleva ese número. Cuando mercadofresco-alb-latencia-alta salte a las 19:20 de un viernes, la primera pregunta será «¿qué versión hay en producción?» y la segunda «¿qué entró desde la anterior?». Con etiquetas se responden en diez segundos; sin ellas, reconstruyendo fechas y esperanzas.

El .gitignore y por qué nunca se sube un .env

Un .gitignore correcto no es higiene: es control de fugas.

# Entorno y secretos - NUNCA en el repositorio
.env
.env.*
!.env.example
*.pem
*.key
credenciales*.json

# Python y artefactos de construccion
__pycache__/
.venv/
.pytest_cache/
.coverage
dist/
node_modules/

# Datos locales: pueden contener datos personales
*.csv
*.sql
volcado_aurora_*.dump
!datos/ejemplo_anonimo.csv

La línea !.env.example desexcluye una plantilla de variables sin valores, que sí debe estar para que alguien nuevo sepa qué configuración hace falta. Y las últimas líneas evitan que un volcado de Aurora con pedidos reales acabe en el repositorio: eso es un incidente de protección de datos, no un descuido.

Por qué el .env es el peor de todos. En el portátil de Luis hay uno con la contraseña de mfadmin, la clave de la pasarela de pagos y el token del ERP. Si entra una sola vez en el repositorio:

queda para siempre en la historia —borrarlo en el commit siguiente no lo elimina, se recupera con git show, y limpiarlo de verdad exige reescribir la historia y forzar el push rompiendo todos los clones—, se replica en cada clon, caché de CI y copia del proveedor, y la única respuesta válida es rotar las credenciales: si el secreto se expuso está comprometido, y da igual cuánto duró la exposición.

La solución la construimos en 04-03 y aquí solo hay que usarla: contraseñas en mercadofresco/produccion/rds/mfadmin con rotación automática cada 30 días, y configuración no sensible en Parameter Store bajo /mercadofresco/produccion/. La aplicación las lee al arrancar con rol-mercadofresco-tienda. En el repositorio no hay ni un valor secreto, solo el nombre del secreto, que no es sensible. Y la rotación de 04-03 gana aquí un valor que entonces no era evidente: pone fecha de caducidad automática al daño de una fuga. En 08-02 añadiremos una red más: un análisis que falla la construcción si detecta algo con pinta de credencial.

Notificaciones y disparadores

Un repositorio que nadie mira es un repositorio donde las PR esperan cuatro días.

Mecanismo Qué es Casos típicos
Disparadores Configuración del repositorio: en push, invoca SNS o Lambda Automatismos ligados al push
Notificaciones Regla sobre eventos del servicio hacia SNS o un chat Avisar a personas de PR y comentarios
EventBridge Todos los eventos de CodeCommit llegan al bus default Enrutado por contenido
aws codecommit put-repository-triggers \
  --repository-name mercadofresco-tienda \
  --triggers '[{"name":"aviso-push-main",
    "destinationArn":"arn:aws:sns:eu-west-1:111122223333:alertas-mercadofresco",
    "branches":["main"],"events":["updateReference"]}]' \
  --profile mercadofresco-dev --region eu-west-1

Y una regla de EventBridge que aprovecha todo lo de 07-03, con source: ["aws.codecommit"], detail-type: ["CodeCommit Pull Request State Change"] y un filtro por detail.event para reaccionar a pullRequestCreated. Si lo que quieres es avisar a personas, no lo montes con un disparador: para eso están las notificaciones, que traen el formato hecho y se integran con Slack sin escribir código.

Hay un antipatrón que conviene nombrar: usar un disparador para lanzar el despliegue. Es tentador y funciona el primer día, pero no hay construcción, ni pruebas, ni aprobación, ni artefacto versionado, ni forma de reintentar. Es el scp de Luis con más pasos. Lo que debe reaccionar al push es un pipeline, y eso es 08-04.

Migrar el repositorio de Luis

El repositorio local tiene 1.847 commits, tres ramas y un .env rastreado desde el segundo commit.

Paso 1: inventario. git branch -a, git log --oneline | wc -l y git count-objects -vH para saber qué hay y cuánto pesa.

Paso 2: buscar secretos en la historia antes de subir nada. No es opcional.

git log --all --full-history --oneline -- .env
git log --all -p -S "AKIA" --oneline                     # clave de acceso de AWS
git log --all -p -S "BEGIN RSA PRIVATE KEY" --oneline

Luis encuentra .env en 312 commits. Dos caminos: reescribir la historia con git filter-repo si el repositorio va a ser público o compartido con terceros, o subir y rotar si es privado y el equipo es pequeño. MercadoFresco elige lo segundo, pero la rotación es innegociable y se hace antes del push, con aws secretsmanager rotate-secret --secret-id mercadofresco/produccion/rds/mfadmin y regenerando en el panel del proveedor la clave de la pasarela y el token del ERP.

Paso 3: limpiar el árbol de trabajo.

git rm --cached .env                    # deja de rastrearlo, no lo borra del disco
printf '.env\n.env.*\n!.env.example\n' >> .gitignore
git add .gitignore && git commit -m "chore(seguridad): dejar de rastrear .env y anadir .gitignore"

Paso 4 y 5: remoto y subida, con git remote add origin [email protected]:mercadofresco/mercadofresco-tienda.git seguido de git push --mirror origin.

--mirror sube todas las referencias —ramas, etiquetas y notas— tal como están en local, que es lo que quieres en una migración inicial. Dos advertencias: es destructivo en el destino (borra en el remoto lo que no exista en local, así que solo contra un repositorio vacío y solo la primera vez), y después no se vuelve a usar nunca; el día a día es git push origin <rama>. Si prefieres control fino, git push origin --all y git push origin --tags.

Paso 6: verificar clonando en limpio, el paso que se salta la gente y luego lamenta. Clona en /tmp, cuenta los commits —deben ser los 1.847—, revisa ramas y etiquetas, y comprueba que .env no está.

Pasos 7 y 8: crear desarrollo, proteger main en el proveedor (PR obligatoria, una aprobación, sin force push), y que Marta y Sara clonen. El repositorio deja de estar en un portátil el día en que hay al menos dos copias más y alguien ha comprobado que funcionan.

Coste y limpieza

Concepto Coste en MercadoFresco
CodeCommit, 3 usuarios y 240 MB 0 USD (5 usuarios y 50 GB incluidos)
CodeConnections y GitHub con repositorios privados 0 USD
Notificaciones vía SNS Céntimos
Total del gobierno del código ≈ 0 USD/mes

La barrera para adoptar esto nunca es económica: es el hábito. Lo que cuesta dinero empieza en 08-02.

# IRREVERSIBLE y sin papelera: clona con --mirror a un disco antes de ejecutarlo
aws codecommit delete-repository --repository-name pruebas-borrar \
  --profile mercadofresco-dev --region eu-west-1

Errores Comunes y Consejos

Elegir CodeCommit para un proyecto nuevo. No admite nuevos clientes y no recibe funcionalidades. Aprende sus conceptos, despliega en GitHub o GitLab.

Olvidar credential.UseHttpPath true. Produce un 403 al clonar de CodeCommit que parece un problema de permisos IAM y no lo es. Es el primer sitio donde mirar.

Dejar la conexión de CodeConnections en PENDING. El recurso existe, la CLI lo lista y todo lo que dependa de él falla con un mensaje confuso. La autorización en el navegador es obligatoria y manual.

Guardar claves de larga duración como secretos de GitHub. Usa OIDC con una condición sub que nombre tu organización y repositorio; un repo:* es un agujero grave.

Denegar las acciones MergeBranchesBy* para «proteger main». Nadie puede fusionar ni siquiera una PR aprobada, y a los dos días alguien pide quitar la política entera. Protege la escritura directa, no la fusión gobernada.

Ramas de funcionalidad que viven tres semanas. Cada día extra multiplica el coste de fusión. Si el cambio es grande, pártelo en incrementos compatibles hacia atrás. Y adoptar GitFlow por costumbre en un producto de entrega continua añade tres ramas de larga vida a cambio de un problema que no tienes.

Subir un .env «solo un momento para probar». Queda en la historia para siempre y la única respuesta correcta es rotar. No hay atajo.

Fusionar con squash y luego intentar revertir un commit intermedio. Ya no existe: la rama entera es un solo commit. Es el precio consciente del squash.

Consejo: escribe el CONTRIBUTING.md el primer día, y usa .github/pull_request_template.md para precargar las cuatro secciones. Media página ahorra las mismas cinco conversaciones cada mes, y un revisor con contexto revisa el doble de rápido.

Consejo: activa el borrado automático de ramas al fusionar, y etiqueta cada versión que llega a producción sin excepciones: el día del incidente, la diferencia entre diez segundos y veinte minutos de diagnóstico está en esa etiqueta.

Ejercicios

Ejercicio 1: elegir proveedor con criterio

Para cada escenario, elige entre CodeCommit, GitHub, GitLab autoalojado o Bitbucket y justifícalo en tres frases nombrando el criterio decisivo:

(a) Empresa pública española, 40 desarrolladores. El pliego exige que el código resida en infraestructura bajo su control dentro de la UE con auditoría completa de accesos. Tienen cuenta AWS desde 2019 con CodeCommit habilitado y 60 repositorios.

(b) Startup de 6 personas que arranca hoy. Todos vienen de GitHub, quieren escaneo de dependencias sin montarlo y desplegarán en AWS con Lambda y S3.

(c) Equipo de 25 personas en una empresa que usa Jira para todo, con auditorías trimestrales que cruzan incidencias con commits.

Ejercicio 2: la política que bloqueó al equipo

Marta aplica esto al grupo desarrolladores-mercadofresco para proteger main:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Deny",
    "Action": "codecommit:GitPush",
    "Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
    "Condition": {
      "StringNotEqualsIfExists": { "codecommit:References": ["refs/heads/main"] }
    }
  }]
}

Al día siguiente Luis no puede empujar a su rama de funcionalidad, pero sí puede empujar a main. Responde: (a) qué hace exactamente y por qué produce ese efecto; (b) escríbela correctamente; (c) qué le falta para proteger main de verdad; (d) qué comprobación habría detectado el fallo antes de aplicarla.

Ejercicio 3: la migración con sorpresa

Al ejecutar el paso 2, Luis encuentra: un .env en 312 commits con la contraseña de mfadmin y la clave de la pasarela; un clientes_octubre.csv de 18 MB con 4.200 nombres, direcciones y teléfonos reales, añadido hace nueve meses; y una rama experimento/pagos-bizum de hace un año sin fusionar.

Responde: (a) qué haces con cada hallazgo y en qué orden; (b) cuál hace que reescribir la historia deje de ser opcional, y por qué; (c) el comando exacto y qué avisas al equipo antes; (d) a quién hay que informar dentro de la empresa por el segundo hallazgo.

Soluciones

Solución 1

(a) CodeCommit, y es el único escenario del ejercicio donde tiene sentido. El criterio decisivo es el primero de la lista: la exigencia legal de residencia y control del código, que anula a los demás. La cuenta es anterior a julio de 2024, así que el servicio está disponible; el código vive en su cuenta de AWS en eu-west-1, cifrado con su clave de KMS, y CloudTrail da la auditoría que pide el pliego. La alternativa sería GitLab autoalojado en su VPC, que cumple igual el requisito pero les obliga a operar y parchear el servidor: con 60 repositorios ya migrados no compensa. Lo que sí deben hacer es planificar la salida a medio plazo, porque están sobre un servicio sin evolución.

(b) GitHub. Criterio decisivo: dónde está ya el equipo, reforzado por el ecosistema. Seis personas que ya conocen la herramienta producen el primer día; Dependabot da el escaneo de dependencias sin montar nada; los repositorios privados son gratuitos a ese tamaño. Para desplegar en AWS, OIDC más CodeConnections cubre todo sin almacenar claves, y no hay restricción de residencia que lo complique.

(c) Bitbucket. Criterio decisivo: la integración con el resto de herramientas. La vinculación nativa entre commits, ramas e incidencias de Jira es exactamente lo que consume su auditoría trimestral; con GitHub habría que montar y mantener esa integración. GitLab sería superior en CI/CD, pero aquí el ahorro está en el proceso, no en el pipeline; y con 25 personas Bitbucket es además el más barato.

Solución 2

(a) La condición está invertida. StringNotEqualsIfExists con refs/heads/main significa «deniega cuando la referencia no sea main». El resultado es literalmente el contrario del deseado: prohíbe empujar a cualquier rama excepto main y deja main completamente abierta. Es un error de una sola palabra —Not— con consecuencias opuestas a la intención, y por eso las denegaciones con condiciones negadas hay que leerlas dos veces en voz alta.

(b) La correcta usa StringEqualsIfExists para acotar la denegación a main, añade Null para que no afecte a operaciones sin referencia, y amplía las acciones:

{
  "Version": "2012-10-17",
  "Statement": [{
    "Sid": "DenegarEscrituraDirectaEnMain",
    "Effect": "Deny",
    "Action": ["codecommit:GitPush", "codecommit:DeleteBranch",
               "codecommit:PutFile", "codecommit:DeleteFile"],
    "Resource": "arn:aws:codecommit:eu-west-1:111122223333:mercadofresco-tienda",
    "Condition": {
      "StringEqualsIfExists": { "codecommit:References": ["refs/heads/main"] },
      "Null": { "codecommit:References": "false" }
    }
  }]
}

(c) DeleteBranch impide borrar main entera; PutFile y DeleteFile cierran la edición desde la consola web, que salta Git y es el agujero más olvidado. Un push --force ya queda cubierto porque IAM no lo distingue de un GitPush. Pero falta lo que ninguna política puede dar: una regla de aprobación asociada al repositorio que exija al menos una aprobación en las PR hacia main, más —cuando exista— la comprobación de que la construcción está en verde. Sin eso, la política solo obliga a pasar por una PR; no obliga a que la PR sea buena.

(d) El simulador de políticas de IAM de 04-01, o mejor: aplicarla primero a un repositorio de pruebas y ejecutar los cuatro casos —push a rama de funcionalidad, push a main, borrado de rama, edición desde la consola— comprobando que dos pasan y dos fallan. Una denegación con condiciones nunca debe estrenarse en el repositorio real: el modo de fallo es que bloquea al equipo o, peor, que no bloquea nada y nadie se entera.

Solución 3

(a) El orden importa porque el primer paso hace irrelevante el tiempo de los demás. Uno: rotar inmediatamente los secretos del .env —contraseña de mfadmin desde Secrets Manager, clave de la pasarela regenerada en el panel del proveedor— antes de tocar el repositorio; están comprometidos desde hace catorce meses y el borrado no los descompromete. Dos: el CSV, decidir la reescritura y no subir nada mientras tanto. Tres: la rama experimento/pagos-bizum no se migra; si tiene valor se exporta con git format-patch y se guarda fuera. Migrar ramas muertas es arrastrar ruido.

(b) El CSV. El .env es grave pero acotado: se resuelve rotando, porque un secreto rotado ya no vale nada aunque siga en la historia. Un fichero con 4.200 nombres, direcciones y teléfonos reales no se puede rotar. Es dato personal bajo el RGPD, y el principio de minimización más el derecho de supresión hacen inaceptable conservarlo indefinidamente en un repositorio de código, donde se replicará en cada clon, cada caché de CI y cada copia del proveedor. Aquí reescribir deja de ser una opción técnica y pasa a ser una obligación.

(c)

cp -r ~/proyectos/tienda ~/proyectos/tienda-copia-seguridad   # filter-repo es irreversible
cd ~/proyectos/tienda
git filter-repo --path datos/clientes_octubre.csv --invert-paths
git filter-repo --path .env --invert-paths                    # de paso, ya que se reescribe

git filter-repo cambia el hash de los commits afectados y de todos sus descendientes, así que hay que avisar de que cualquier clon existente queda inservible y debe reclonarse: fusionar un clon antiguo con la historia reescrita crea un desastre de commits duplicados. Aquí la migración aún no ha ocurrido y solo existe el clon de Luis, así que el impacto es nulo —razón excelente para limpiar antes de subir—. Si ya estuviera en el remoto, además haría falta push --force, borrar ramas y etiquetas obsoletas del remoto y pedir al proveedor que purgue los objetos inalcanzables.

(d) Al responsable de protección de datos (DPO o equivalente). Datos personales de 4.200 clientes fuera de los sistemas autorizados durante nueve meses es un tratamiento no previsto que hay que documentar y evaluar. Puede que no sea una brecha notificable a la AEPD —el repositorio era privado y local—, pero esa valoración no la hace el equipo técnico: se aporta la información completa (qué datos, cuánto tiempo, quién tuvo acceso, qué se ha hecho) y decide quien tiene la responsabilidad. Es el mismo criterio que aplicamos en 06-03 con el clon aurora-mf-pruebas-luis.

Conclusión

El código de MercadoFresco ha salido del portátil de Luis. Ya no es un git init con una copia USB de septiembre: es un repositorio remoto, replicado, cifrado, con permisos de IAM en lugar de usuarios de sistema y con al menos tres copias vivas en el equipo. Los cuatro primeros riesgos del inventario de Marta están cerrados por el simple hecho de haber subido el código.

Pero lo importante de esta lección no es el alojamiento, es el gobierno. Sabes que CodeCommit ya no admite nuevos clientes y por qué eso no invalida aprenderlo —te lo vas a encontrar—, y que la respuesta actual es GitHub o GitLab conectados con CodeConnections, con su autorización manual en el navegador que tantas acciones de origen rompe cuando se olvida. Tienes el criterio de elección en orden: primero la exigencia legal si existe, luego dónde está ya tu equipo, luego dónde quieres que viva el CI/CD, y por último el ecosistema. Sabes autenticarte con el ayudante de credenciales —con credential.UseHttpPath true, que evita el 403 misterioso— y con OIDC en lugar de claves de larga duración para las máquinas. Y sabes escribir una política que protege main sin bloquear al equipo, y por qué la condición Null acompaña siempre a StringEqualsIfExists.

Has elegido trunk-based con una rama desarrollo declarada como andamio temporal, porque una tienda web solo tiene una versión —la que está en producción ahora— y GitFlow resuelve un problema que MercadoFresco no tiene. Sabes qué es una PR que sirve —qué, por qué, cómo probarlo, riesgos—, cuándo usar squash y cuándo three-way, y cómo una plantilla de regla de aprobación convierte la revisión en un control real gracias a que el autor no puede aprobarse a sí mismo. Tienes las convenciones que un pipeline leerá más adelante: commits convencionales, versión semántica y etiquetas que responden en diez segundos a «qué hay en producción». Y tienes la regla que no admite excepciones: el .env nunca sube, porque queda en la historia para siempre y la única respuesta válida es rotar.

Ahora hay un repositorio con reglas. Lo que no hay es ninguna garantía de que lo que entra en él funcione. La aprobación de la PR significa que una persona ha leído el diff un martes por la tarde, lo cual es mucho más que nada y bastante menos que suficiente. Nadie ha ejecutado las pruebas: Luis las lanza en su portátil «casi siempre», y la última vez que falló una decidió que era cosa de su entorno. Nadie ha comprobado que las dependencias no tengan vulnerabilidades conocidas, ni que no se haya colado una clave en el código, ni que el proyecto siquiera arranque en una máquina limpia. La frase «funciona en mi portátil» sigue siendo un argumento admisible en MercadoFresco, y todavía no existe ningún artefacto: lo que se despliega es una carpeta copiada con scp.

En 08-02, «AWS CodeBuild», eso se acaba. Veremos qué es la integración continua y por qué convierte «funciona en mi portátil» en una frase sin valor; la anatomía de un proyecto de construcción con su entorno, su rol de servicio, su caché y sus artefactos; un buildspec.yml completo y comentado con las fases install, pre_build, build y post_build; cómo inyectar secretos desde Parameter Store y Secrets Manager sin que aparezcan en los registros; las pruebas unitarias con pytest y los informes de cobertura como puerta de calidad y no como informe que nadie lee; el escaneo de dependencias y de credenciales filtradas; la construcción del paquete que irá al bucket mercadofresco-artefactos; y cómo ejecutar la construcción dentro de vpc-mercadofresco para probar contra el clon aurora-mf-pruebas-luis, con la advertencia de coste que eso trae.

Curso de AWS

Módulo 1: Introducción a AWS

Módulo 2: Servicios principales de AWS

Módulo 3: Redes y entrega de contenido

Módulo 4: Seguridad e identidad

Módulo 5: Monitorización y gestión

Módulo 6: Bases de datos

Módulo 7: Integración de aplicaciones

Módulo 8: Herramientas para desarrolladores

Módulo 9: Infraestructura como código y gobierno de cuentas

Módulo 10: Contenedores en AWS

Módulo 11: Mejores prácticas y gestión de costos

© Copyright 2026. Todos los derechos reservados