Un recurso necesita un número variable de bloques anidados idénticos (por ejemplo, reglas de entrada) impulsados por una lista/mapa.
→Use un bloque `dynamic` cuyo `for_each` itere la colección; haga referencia a cada elemento a través del iterador de bloque (nombre predeterminado = etiqueta del bloque) dentro de `content {}`.
Por qué: Los bloques dinámicos generan bloques anidados repetidos sin copiar y pegar; el iterador mantiene cada bloque generado vinculado a su elemento de origen.
Referencia↗
Cree un recurso por cada entrada en un mapa de objetos, con claves estables para que la reordenación nunca fuerce el reemplazo.
→Establezca `for_each = var.objects` (un mapa). Use `each.key` para la clave estable y `each.value.<attr>` para los campos. Evite `count` aquí, los cambios de índice causan inestabilidad.
Por qué: Las claves de mapa son identidades estables en el estado; los índices de lista son posicionales y cambian cuando se añaden o eliminan elementos.
Decida entre `count` y `for_each` para múltiples instancias.
→Use `for_each` cuando las instancias tienen identidades distintas (un conjunto/mapa); use `count` solo para N copias idénticas e insensibles al orden. Prefiera `for_each` para cualquier cosa que pueda crecer/disminuir.
Por qué: `for_each` se dirige por clave (resource["key"]); `count` se dirige por índice (resource[0]), lo que se reorganiza en inserciones/eliminaciones.
Una variable de entrada es un objeto donde algunos atributos son opcionales y necesitan valores predeterminados.
→Escríbalo como `object({ name = string, size = optional(number, 10) })`. `optional(type, default)` proporciona el valor predeterminado cuando el llamador omite el atributo.
Por qué: `optional()` con un valor predeterminado mantiene a los llamadores concisos mientras garantiza un valor concreto en el flujo de trabajo, sin manejar nulos en todas partes.
Referencia↗
Valide una suposición sobre un recurso antes de aplicar, o garantice un resultado después.
→Use `lifecycle { precondition { ... } }` para afirmar las entradas antes de crear/actualizar, y `postcondition` para afirmar las salidas después. Ambos toman `condition` + `error_message`.
Por qué: Las condiciones personalizadas fallan rápidamente con un mensaje claro en lugar de producir una aplicación rota o un error posterior confuso.
Referencia↗
Un recurso debe recrearse cada vez que otro recurso o atributo cambia.
→Añada `lifecycle { replace_triggered_by = [aws_x.y.id] }`. Cuando el valor referenciado cambia, Terraform fuerza el reemplazo de este recurso.
Por qué: Expresa una dependencia de reemplazo de forma declarativa, evitando el `-replace` manual en cada cambio relacionado.
Reemplazar un recurso causa tiempo de inactividad porque el antiguo es destruido antes de que exista el nuevo.
→Establezca `lifecycle { create_before_destroy = true }` para que Terraform aprovisione primero el reemplazo, y luego destruya el antiguo. Asegure nombres únicos/sin conflictos difíciles.
Por qué: Reemplazo sin tiempo de inactividad; pero tenga cuidado con las colisiones de nombres y los límites de cuota mientras ambos existen brevemente.
Rechace valores de entrada inválidos tempranamente (por ejemplo, un entorno que no sea dev/stage/prod).
→Añada un bloque `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` a la variable.
Por qué: Detecta entradas incorrectas en tiempo de planificación con un mensaje legible en lugar de fallar profundamente en una llamada al proveedor.
Haga referencia a un valor que podría no existir sin que falle el plan.
→Use `try(local.maybe.value, "default")` para recurrir en caso de errores, o `can(expr)` para obtener un booleano que indique si una expresión tiene éxito.
Por qué: Manejo elegante de datos opcionales/con forma variable; evita "Error: Unsupported attribute" en claves ausentes.
Transforme una lista en un mapa, o filtre/moldee una colección para un argumento de recurso.
→Use una expresión `for`: `{ for u in var.users : u.name => u.role if u.active }` (mapa) o `[for x in list : upper(x)]` (lista).
Por qué: Las expresiones `for` son la forma idiomática de remodelar datos; la cláusula `if` filtra, la forma `k => v` construye mapas.
Una variable o salida contiene un secreto que no debe imprimirse en la salida de plan/aplicación.
→Marque la variable `sensitive = true` (y también las salidas). Terraform la oculta en la salida de CLI, aunque todavía se almacena en el estado.
Por qué: Evita la divulgación accidental en los registros/salida de CI; el estado en sí mismo aún debe protegerse (backend cifrado, control de acceso).
Administre recursos en dos regiones/cuentas dentro de una misma configuración.
→Declare proveedores con alias (`provider "aws" { alias = "west" region = "us-west-2" }`) y establezca `provider = aws.west` en los recursos o pásselo a los módulos.
Por qué: Los alias permiten que una configuración apunte a múltiples instancias de proveedor; los módulos los reciben explícitamente a través del argumento `providers`.
Una dependencia oculta (no expresada a través de referencias) causa problemas de ordenación.
→Añada `depends_on = [aws_iam_role_policy.x]` para forzar la ordenación. Úselo con moderación — prefiera dependencias implícitas a través de referencias de atributos.
Por qué: `depends_on` explícito maneja las dependencias que el gráfico no puede inferir, pero el uso excesivo crea planes conservadores y más lentos.
Renderice un archivo de configuración/datos de usuario desde una plantilla con variables estructuradas.
→Use `templatefile("${path.module}/tpl.tftpl", { items = local.items })`; la plantilla usa interpolación `%{ for }` / `${}`.
Por qué: `templatefile` mantiene la renderización pura/en tiempo de planificación (a diferencia del proveedor de plantillas obsoleto) y admite bucles/condicionales.
Construya una lista plana de cada combinación (subred, regla) para alimentar un solo `for_each`.
→Use `setproduct(var.subnets, var.rules)` para el producto cartesiano, o `flatten([for ...])` para colapsar listas anidadas en una sola.
Por qué: Estas funciones transforman datos anidados en la colección plana y con claves únicas que `for_each` requiere.