Portada del artículo: Renombrar muchos archivos con Python o con un agente de IA

Renombrar muchos archivos con Python o con un agente de IA

Tienes 300 fotos que empiezan con 20240703_ y quieres quitar ese prefijo. Hacerlo una por una es tedioso y es justo el tipo de tarea que un computador hace mejor que tú. Esta es, sin duda, una de las razones por las que me encanta Python: permite automatizar pequeñas tareas y optimizar el tiempo.

En este artículo vemos dos maneras de resolverlo: un script de Python que puedes leer, probar y repetir, y un agente de IA al que le describes lo que quieres. También cuándo conviene cada una y qué riesgos tiene.

Para quién es: para quien nunca ha automatizado una tarea con archivos, aunque sepa muy poco de programación. Qué te llevas: un script corto probado con archivos de ejemplo, un prompt listo para copiar y un criterio para elegir entre ambos caminos.

[!NOTE] Respuesta rápida: guarda el script de abajo como renombrar.py y ejecútalo primero sin --aplicar: te muestra qué cambiaría sin tocar nada. Si el plan te convence, repítelo con --aplicar. Si prefieres un agente de IA, trabaja sobre una copia de la carpeta y pídele que muestre el plan antes de ejecutar.


Lo que necesitas antes de empezar

Los comandos se escriben en la terminal: PowerShell en Windows, la app Terminal en macOS y Linux. Necesitas Python 3 instalado. En macOS y Linux el comando suele ser python3; en Windows puedes usar python o py. El script usa solo la biblioteca estándar, así que no hay que instalar nada con pip.

Un script es simplemente un archivo de texto con instrucciones que el computador ejecuta de arriba abajo. Aquí el archivo se llamará renombrar.py.


Por qué el script típico es peligroso

La idea más simple, y la que yo mostraba en la primera versión de este artículo, es: listar los archivos de la carpeta, quedarse con los que terminan en .jpg, quitarles el texto sobrante con .replace() y renombrarlos con os.rename. Funciona en la demo. Con archivos reales tiene tres problemas:

1. Puede sobrescribir archivos sin avisar

Según la documentación de Python, Path.rename() en Unix (Linux y macOS) reemplaza el destino en silencio si ya existe y es un archivo; en Windows, en cambio, lanza FileExistsError. Lo mismo ocurre con os.rename. Es decir, el mismo script se comporta distinto según el sistema, y en uno de ellos puede destruir datos.

Un ejemplo claro: tienes 20240703_a.jpg y también a.jpg. Al quitar el prefijo, ambos quieren llamarse a.jpg. En Linux o macOS el segundo archivo desaparece sin ningún mensaje.

2. No reconoce todas las extensiones

Comparar con endswith(".jpg") ignora FOTO.JPG y foto.jpeg. Las cámaras y teléfonos suelen escribir las extensiones en mayúsculas. El resultado es el peor tipo de falla: el script termina sin errores y sin haber hecho todo el trabajo.

3. No hay vista previa

Si te equivocas en el texto a quitar, el daño ya está hecho cuando lo notas. Lo sensato es mostrar primero el plan y ejecutar solo cuando lo confirmes. En inglés a esto se le llama dry run, un simulacro de la operación.


El script clásico, corregido

Este script usa solo pathlib, la biblioteca de Python que trata las rutas de archivos como objetos y no como simples textos, lo cual es muy cómodo si estás empezando (pathlib). Recibe la carpeta y el texto que quieres quitar, hace un simulacro por defecto, compara extensiones sin distinguir mayúsculas, omite las colisiones y al final imprime un resumen. Guárdalo como renombrar.py:

import argparse
import sys
from collections import Counter
from pathlib import Path


def main() -> int:
    parser = argparse.ArgumentParser(description="Quita un texto del nombre de varios archivos.")
    parser.add_argument("carpeta", type=Path, help="carpeta con los archivos")
    parser.add_argument("quitar", help="texto que se elimina del nombre, por ejemplo 20240703_")
    parser.add_argument("--ext", default=".jpg,.jpeg", help="extensiones a procesar, separadas por comas")
    parser.add_argument("--aplicar", action="store_true", help="renombra de verdad (sin esto solo muestra el plan)")
    args = parser.parse_args()

    if not args.carpeta.is_dir():
        sys.exit(f"No es una carpeta: {args.carpeta}")
    # ".jpg" o "jpg" valen igual; las entradas vacías se ignoran
    extensiones = {"." + e.strip().lower().lstrip(".") for e in args.ext.split(",") if e.strip()}

    # 1. Plan: (archivo, nuevo nombre). Solo se cambia el nombre, nunca la extensión
    plan = []
    for archivo in sorted(args.carpeta.iterdir()):
        if archivo.is_file() and archivo.suffix.lower() in extensiones:
            nuevo = archivo.stem.replace(args.quitar, "")
            if nuevo and nuevo != archivo.stem:
                plan.append((archivo, archivo.with_name(nuevo + archivo.suffix)))

    # 2. Colisiones: el destino ya existe o dos archivos terminarían con el mismo nombre
    repetidos = Counter(destino.name.lower() for _, destino in plan)
    contadores = {"renombrados": 0, "omitidos": 0, "fallidos": 0}
    for origen, destino in plan:
        # samefile: en macOS y Windows "Foto.jpg" y "foto.jpg" son el mismo archivo.
        # is_symlink: un enlace simbólico en el destino (incluso roto) cuenta como ocupado.
        ocupado = destino.is_symlink() or (destino.exists() and not destino.samefile(origen))
        if ocupado or repetidos[destino.name.lower()] > 1:
            print(f"OMITIDO   {origen.name} -> {destino.name} (colisión)")
            contadores["omitidos"] += 1
            continue
        if args.aplicar:
            try:
                origen.rename(destino)  # ya comprobamos que destino no existe
            except OSError as error:  # permisos, archivo en uso, cambios en la carpeta...
                print(f"FALLÓ     {origen.name}: {error}")
                contadores["fallidos"] += 1
                continue
        print(f"{'RENOMBRA' if args.aplicar else 'SIMULA':9} {origen.name} -> {destino.name}")
        contadores["renombrados"] += 1

    modo = "aplicado" if args.aplicar else "simulacro, no se tocó nada (usa --aplicar)"
    print(f"\n{contadores['renombrados']} a renombrar, {contadores['omitidos']} omitidos, "
          f"{contadores['fallidos']} fallidos: {modo}")
    return 1 if contadores["omitidos"] or contadores["fallidos"] else 0


if __name__ == "__main__":
    sys.exit(main())

Dónde y cómo usarlo: guarda el archivo como renombrar.py y abre la terminal en la carpeta donde lo guardaste. Cambia ./fotos por la ruta de tu carpeta (en Windows, por ejemplo, C:\Users\tu-usuario\Pictures\fotos; ponla entre comillas si tiene espacios). En macOS y Linux escribe python3 en lugar de python.

Cómo se usa

Primero el simulacro, que no toca nada:

python renombrar.py ./fotos 20240703_

Si el plan se ve bien, el mismo comando con --aplicar:

python renombrar.py ./fotos 20240703_ --aplicar

Por defecto procesa .jpg y .jpeg. Para otras extensiones usa --ext, por ejemplo --ext .png,.webp.

Dónde y cómo usarlo: ejecuta estos comandos desde la terminal, abierta en la carpeta del script, y cambia 20240703_ por el texto que quieres quitar. La carpeta no se recorre de forma recursiva: solo se procesan los archivos de ese nivel. La extensión se compara sin distinguir mayúsculas, así que .JPG y .jpeg también cuentan.

Lo que hace diferente a esta versión

  • Simulacro por defecto. Sin --aplicar solo imprime SIMULA vieja -> nueva. Esto te permite ver el plan completo antes de cambiar nada.
  • Compara extensiones en minúsculas. archivo.suffix.lower() cubre .jpg, .JPG y .Jpg.
  • Omite colisiones. Si el destino ya existe, o si dos archivos terminarían con el mismo nombre, los marca como OMITIDO y no los toca. Así nunca depende de lo que haga rename en tu sistema operativo.
  • Nunca toca la extensión. El texto se quita solo del nombre (archivo.stem), así que --ext y la extensión del archivo quedan intactos.
  • Ignora lo que no cambia. Si el nombre no contiene el texto a quitar, o si quedaría vacío, el archivo no entra al plan.
  • Resumen final y errores controlados. Cuenta los renombrados, omitidos y fallidos. Si el sistema rechaza un cambio (por permisos o porque un archivo está en uso), lo marca como FALLÓ y sigue con el resto en vez de detenerse a mitad. Termina con código 0 si todo salió limpio y 1 si hubo omitidos o fallos, útil si lo usas dentro de otro script.

Cómo se probó

El script se probó con una carpeta de ejemplo (Python 3.13 en Linux) que contenía 20240703_a.jpg, 20240703_b.JPG, 20240703_c.png, 20240703_d.jpeg, a.jpg y notas.txt. Estos son los resultados:

Tabla 1: Resultado del script sobre la carpeta de ejemplo

ArchivoResultado
20240703_a.jpgOmitido: a.jpg ya existe (colisión)
20240703_b.JPGRenombrado a b.JPG (la extensión en mayúsculas cuenta)
20240703_d.jpegRenombrado a d.jpeg
20240703_c.pngSin cambios (.png no está en la lista por defecto)
a.jpg, notas.txtSin cambios

Cómo leerla: lo importante es la primera fila. El archivo en colisión se omite y el contenido de a.jpg sigue intacto; se comprobó el contenido de cada archivo después de aplicar los cambios. El simulacro imprimió exactamente el mismo plan y no modificó ningún archivo.


La otra opción: pedírselo a un agente de IA

Hoy puedes obtener el mismo resultado sin escribir una línea de código: abres un agente de IA de programación en tu carpeta, describes lo que quieres y él escribe y ejecuta los comandos. Herramientas como Claude Code, OpenAI Codex y Google Antigravity funcionan así.

Lo que cambia respecto al script:

  • Tú describes el resultado, no el procedimiento. Eso es más rápido de empezar, pero depende de que expliques bien el caso.
  • El agente decide cómo hacerlo. Puede escribir un script como el de arriba, usar comandos del sistema o editar los nombres uno por uno. Cada ejecución puede ser distinta.
  • El agente ejecuta comandos en tu carpeta. Esa es la diferencia que más importa y la veremos con cuidado en la siguiente sección.

Un prompt para copiar

Un prompt claro tiene contexto, una tarea concreta, ejemplos y restricciones. Las etiquetas ayudan a separar cada parte, como explico en la guía práctica de ingeniería de prompts. Puedes copiar este y cambiar lo que está entre corchetes:

<contexto>
Estoy en la carpeta [./fotos]. Tiene fotos .jpg, .JPG y .jpeg, y otros archivos que no quiero tocar.
</contexto>

<tarea>
Quita el texto "[20240703_]" del nombre de las fotos.
Ejemplo: 20240703_playa.jpg -> playa.jpg
</tarea>

<restricciones>
- Antes de cambiar nada, muéstrame el plan completo (nombre actual -> nombre nuevo) y espera mi confirmación.
- No sobrescribas ningún archivo. Si un nombre nuevo ya existe o dos archivos quedarían con el mismo nombre, omítelos y avísame.
- Procesa las extensiones sin distinguir mayúsculas y no toques otros tipos de archivo.
- Guarda un registro (por ejemplo mapeo.csv) con "nombre viejo,nombre nuevo" para poder deshacer el cambio.
- Prefiere escribir un script de Python con pathlib en lugar de ejecutar comandos sueltos, para que yo pueda revisarlo y repetirlo.
</restricciones>

Dónde y cómo usarlo: abre la terminal (o el editor con el agente) en la carpeta de trabajo, o mejor, en una copia de ella. Inicia el agente (en Claude Code, el comando claude; en Codex, codex; Antigravity es un editor con panel de agente) y pega el prompt. Cambia lo que está entre corchetes. El mismo prompt sirve en las tres herramientas, y funciona igual en Windows, macOS y Linux porque le pides un script de Python en vez de comandos propios de un sistema.

El riesgo real: el agente ejecuta comandos

Un chat solo te responde con texto. Un agente actúa: lee tus archivos, ejecuta comandos y los modifica. Si se equivoca de carpeta, interpreta mal un patrón o un nombre, el daño es real. Estas son las medidas que recomiendo:

  1. Trabaja sobre una copia de la carpeta, o dentro de un repositorio de Git para poder volver atrás con un commit. Si Git es nuevo para ti, empieza por qué es Git y por qué importa al programar con IA. La documentación de Codex recomienda precisamente un flujo con control de versiones: una rama de trabajo, git status limpio antes de delegar y commits frecuentes.
  2. Pide siempre el plan antes de ejecutar y revísalo. Con 300 archivos, mira una muestra y busca casos raros.
  3. Usa el modo de aprobación de la herramienta y no lo desactives. Cada una lo llama distinto, así que conviene leer la documentación oficial:
    • Claude Code: en el modo por defecto (llamado Manual en la CLI) pide permiso antes de ejecutar comandos de shell, salvo un conjunto de comandos de solo lectura. El modo acceptEdits acepta automáticamente las ediciones de archivos y comandos como mv o cp dentro de la carpeta de trabajo: cómodo, pero precisamente por eso conviene no activarlo hasta confiar en el plan.
    • OpenAI Codex: combina un sandbox (qué puede hacer técnicamente) con una política de aprobación (cuándo debe preguntarte). Su preajuste Auto permite leer, editar y ejecutar comandos dentro del directorio de trabajo sin preguntar, y pide aprobación para salir de él o usar la red; al abrirlo recomienda Auto para carpetas con control de versiones y read-only para las que no lo tienen.
    • Google Antigravity: su ajuste de ejecución de comandos de terminal tiene la opción Request Review, que pide confirmación antes de ejecutar cualquier comando (salvo los de tu lista de permitidos), y Always Proceed, que los ejecuta sin preguntar (salvo los de tu lista de denegados) y la propia documentación describe como de alta autonomía y alto riesgo.
  4. Guarda el mapeo viejo → nuevo. Con ese registro puedes deshacer el cambio aunque no uses Git.

[!WARNING] Estos modos y nombres cambian con las versiones de cada herramienta. Revisa la documentación oficial (enlaces al final) antes de confiar en un ajuste. Y recuerda que un modo de aprobación es una ayuda, no una garantía: el agente puede equivocarse aunque tú apruebes cada paso, por eso la copia o el repositorio siguen siendo tu red de seguridad.


¿Script o agente? Cuándo usar cada uno

Tabla 2: Script de Python frente a agente de IA para renombrar archivos

CriterioScript de PythonAgente de IA
ControlTotal: ves cada línea y cada reglaDepende de que revises el plan y los comandos
ReproducibilidadAlta: el mismo script da el mismo resultadoVariable, salvo que le pidas que guarde un script
Rapidez para empezarMedia: hay que escribirlo o adaptarlo una vezAlta: describes el caso y arranca
Reglas complejasTú las programasLas interpreta a partir de ejemplos
RiesgoBajo con simulacro y comprobación de colisionesMayor: ejecuta comandos en tu carpeta
CostoGratisRequiere una suscripción o uso de API de la herramienta

Cómo leerla: cada fila compara una cualidad. El script gana en control, repetición y costo; el agente gana en velocidad cuando la regla no es obvia o cuando no quieres programar. No hay un ganador absoluto: depende de si la tarea se repetirá y de cuánto riesgo toleras.

Mi criterio:

  • Usa el script si la regla es simple, si vas a repetirla (por ejemplo cada vez que llegan fotos nuevas) o si los archivos son importantes y no tienes una copia.
  • Usa el agente para una regla irregular que cuesta expresar en código (“pon la fecha al principio y deja el resto en minúsculas, pero solo en las fotos de la carpeta de eventos”), o para una tarea única que no justifica escribir nada.
  • Combina ambos: pídele al agente que escriba el script (como en el prompt de arriba), revísalo tú y guárdalo. Así tienes la rapidez del agente y la repetibilidad del script.

Adaptar el script a otras reglas

El corazón del script es una sola línea: nuevo = archivo.stem.replace(args.quitar, ""). Trabaja sobre el nombre sin la extensión, que se vuelve a pegar después. Cambia esa línea y obtienes otra regla:

  • Agregar un prefijo: nuevo = "vacaciones_" + archivo.stem
  • Pasar a minúsculas: nuevo = archivo.stem.lower()
  • Cambiar espacios por guiones: nuevo = archivo.stem.replace(" ", "-")

Dónde y cómo usarlo: estas son líneas sueltas que sustituyen a la línea nuevo = ... dentro de renombrar.py, en la sección “Plan”. El resto del script (simulacro, colisiones y resumen) sigue funcionando igual. Si cambias la regla, el argumento quitar deja de usarse, pero sigue siendo obligatorio al ejecutar: pásale cualquier valor (por ejemplo python renombrar.py ./fotos x) o borra su línea add_argument. Ejecuta siempre el simulacro primero. La regla de minúsculas se probó en Linux; en macOS y Windows, donde Foto.jpg y foto.jpg son el mismo archivo, el script lo detecta con samefile y no lo marca como colisión, pero ese caso no se probó en esos sistemas.


En resumen

  • Path.rename reemplaza en silencio un archivo existente en Linux y macOS y falla en Windows: comprueba siempre que el destino no exista.
  • Compara extensiones con suffix.lower() y muestra un simulacro antes de aplicar.
  • Un agente de IA es útil para reglas irregulares o tareas únicas, pero ejecuta comandos en tu carpeta: usa una copia o Git, pide el plan y no desactives la aprobación de comandos.
  • Lo mejor de ambos mundos: que el agente escriba un script, lo revises y lo guardes.

Tu primer paso

Crea una carpeta de prueba con cinco archivos falsos (touch a.jpg b.JPG en macOS y Linux, o créalos a mano en Windows), guarda el script y ejecuta el simulacro. Cuando entiendas cada línea del plan que imprime, pruébalo con tus archivos reales, siempre sobre una copia.

Si en tu empresa hay tareas repetitivas con archivos (renombrar, convertir, ordenar, extraer datos) y quieres automatizarlas, escríbeme a contact@camilordz.com.


Fuentes y lecturas recomendadas

RUTA DE LECTURA RECOMENDADA

El siguiente paso: cómo llevar esto a la práctica

Guías de laboratorio y arquitecturas técnicas para resolver este problema sin rodeos.

EXPLORAR CATEGORÍA COMPLETA→