Módulo 2: Trabajar con archivos y texto

8. Proyecto: auditar un proyecto que no conoces

Descripción

En algún momento de tu primer trabajo alguien te va a asignar un repositorio que no escribiste, sin documentación, y te va a decir apenas "fíjate qué tiene y avísame si encuentras algo raro". Nadie te va a explicar la estructura línea por línea. En esta lección vas a simular exactamente esa situación en dos partes. Primero reconstruyes un árbol de proyecto a partir de un mapa, usando la menor cantidad de comandos posible con mkdir -p y touch. Después lo auditas de verdad: respondes cinco preguntas concretas con el comando exacto que usaste y el resultado que produjo —no con una impresión general de lo que viste—. Al cerrar, aplicas la limpieza segura que aprendiste en la lección 3 para dejar tu directorio de práctica como estaba.

Esto no es un ejercicio de relleno. Auditar antes de tocar es lo que separa a quien rompe algo en su primera semana de quien no: cuando heredas código, tu primera responsabilidad no es escribir, es entender qué hay y dejar evidencia escrita de lo que encontraste, para que la siguiente persona —que podrías ser tú mismo dentro de seis meses, con todo esto olvidado— no tenga que repetir el trabajo de reconocimiento desde cero.

Conexión con el módulo: esta es la última lección del módulo y no introduce herramientas nuevas, con una sola excepción puntual de touch que necesitas para que la auditoría tenga sentido. Todo lo demás es mkdir/touch (lección 2), la disciplina de "lista antes de borrar" (lección 3), cat y wc -l para medir un archivo (lección 4), find (lección 5), grep (lección 6) y nano (lección 7) trabajando juntos sobre un caso con apariencia real. El módulo 3 te va a enseñar a conectar estos mismos comandos con tuberías (|); hasta que eso pase, cada pregunta de esta auditoría se responde con un comando autónomo y una lectura atenta de su salida. Vas a sentir en carne propia, en la parte 3, por qué el módulo siguiente existe.

Heredar un proyecto es mudarte a una casa sin inventario

Cuando alguien deja un departamento amueblado y te mudas sin que te expliquen nada, no empiezas a redecorar. Primero recorres cada cuarto, revisas qué luces prenden, qué llaves te dieron y qué quedó a medio arreglar en el garage. Recién con esa lista en la mano decides qué tocar primero, y con qué cuidado.

Auditar un proyecto heredado es lo mismo, con una diferencia importante: en una casa confías en lo que ven tus ojos, pero en un árbol de cientos o miles de archivos tus ojos no alcanzan. No puedes "recorrer cada cuarto" abriendo carpeta por carpeta en un explorador de archivos —es exactamente el problema que la introducción del módulo ya te advirtió—. Necesitas comandos que recorran el árbol por ti y te devuelvan evidencia concreta: no "creo que la configuración está por ahí", sino "la configuración está en estos cuatro archivos, y este es el comando que lo demuestra".

El encargo: client-dashboard

Te asignan un proyecto llamado client-dashboard. La persona que lo mantenía renunció hace dos meses. Lo único que tienes es este mapa de su estructura, dibujado a mano en la reunión de traspaso:

client-dashboard/
├── README.md
├── .gitignore
├── .env.example
├── notes.txt
├── config/
│   ├── settings.yaml
│   ├── settings.dev.yaml
│   └── logging.conf
├── src/
│   ├── app.py
│   ├── utils.py
│   └── components/
│       ├── header.py
│       └── footer.py
├── tests/
│   ├── test_app.py
│   └── test_utils.py
└── docs/
    ├── setup.md
    └── architecture.md

Tu tarea tiene tres partes: reconstruir este árbol, llenarlo con lo mínimo necesario para que auditarlo tenga sentido, y auditarlo con evidencia. Antes de empezar, párate en un directorio donde no te importe crear y borrar cosas de práctica:

cd ~
mkdir -p practice
cd practice

Parte 1 — Reconstruir el árbol con el mínimo de comandos

El reto es literal: construye exactamente el árbol de arriba usando la menor cantidad de comandos posible. mkdir y touch son programas distintos —no puedes fusionarlos en una sola línea—, así que el mínimo real son dos comandos: uno arma todas las carpetas, el otro coloca todos los archivos. La herramienta que lo hace posible es la expansión de llaves que viste en la lección 2, incluida su forma anidada.

Paso 1: las carpetas

mkdir -p client-dashboard/{config,src/components,tests,docs}

Anatomía. Bash expande {config,src/components,tests,docs} en cuatro rutas antes de que mkdir las vea: config, src/components, tests y docs, cada una precedida por client-dashboard/. No hace falta listar src por separado: -p crea automáticamente cualquier carpeta intermedia que falte, así que al pedir src/components obtienes src y src/components en el mismo paso.

Verifica con find y el filtro de tipo que ya conoces:

find client-dashboard -type d

Qué esperar (el orden exacto puede variar según tu sistema; lo que importa es que aparezcan las seis rutas):

client-dashboard
client-dashboard/config
client-dashboard/tests
client-dashboard/docs
client-dashboard/src
client-dashboard/src/components

Paso 2: los archivos

Aquí es donde la expansión anidada hace el trabajo pesado: puedes describir archivos que viven en carpetas distintas dentro de una sola llamada a touch.

touch client-dashboard/{README.md,.gitignore,.env.example,notes.txt,config/{settings.yaml,settings.dev.yaml,logging.conf},src/{app.py,utils.py,components/{header.py,footer.py}},tests/{test_app.py,test_utils.py},docs/{setup.md,architecture.md}}

Anatomía. Léelo de afuera hacia adentro. La llave externa tiene ocho elementos separados por coma al nivel más alto: cuatro archivos sueltos (README.md, .gitignore, .env.example, notes.txt) y cuatro grupos con su propia llave interna (config/{...}, src/{...}, tests/{...}, docs/{...}). Dentro de src/{...} hay, a su vez, otra llave anidada para components/{header.py,footer.py}. Bash resuelve las llaves de adentro hacia afuera y te entrega quince rutas completas, todas con el prefijo client-dashboard/ puesto una sola vez.

Verifica:

find client-dashboard -type f

Qué esperar:

client-dashboard/notes.txt
client-dashboard/config/settings.yaml
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/architecture.md
client-dashboard/docs/setup.md
client-dashboard/README.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py
client-dashboard/src/utils.py
client-dashboard/src/app.py

Quince archivos, dos comandos. Escribir cada touch archivo.ext por separado habría sido quince llamadas; con llaves anidadas y bien pensadas, dos. Esa diferencia —de quince a dos— es exactamente el tipo de rendimiento por minuto invertido que la introducción del módulo prometió.

Parte 2 — Dar contenido real para que la auditoría tenga sentido

Ahora mismo tu árbol es técnicamente correcto e inútil para practicar. Todos los archivos están vacíos y todos tienen la fecha de este momento, así que cualquier pregunta sobre "qué se modificó recientemente" o "cuál archivo es más grande" tendría una respuesta trivial y sin valor. Vamos a llenarlo con lo mínimo necesario para que las cinco preguntas de la auditoría tengan una respuesta real, como si el proyecto realmente llevara meses de trabajo detrás.

Escribir el contenido con nano

Vas a usar nano, que viste en la lección anterior: ábrelo con nano <ruta>, pega el contenido de cada bloque, guarda con Ctrl+O seguido de Enter, y sal con Ctrl+X. Repite esto para los seis archivos siguientes. El resto del árbol —las nueve rutas restantes— se queda exactamente como touch las dejó: vacías. Eso es intencional y una de las preguntas de la auditoría depende de ello.

client-dashboard/README.md:

nano client-dashboard/README.md
# Client Dashboard

Panel interno para visualizar métricas de clientes.

## Instalación

Instala las dependencias y corre el servidor de desarrollo.

## Estado

Proyecto heredado, en mantenimiento.

client-dashboard/src/app.py (código en inglés, comentarios en español, como en todo este catálogo):

from utils import calculate_discount, format_currency


def build_invoice(order):
    # TODO: validar que order tenga los campos requeridos antes de procesar
    subtotal = sum(item["price"] * item["quantity"] for item in order["items"])
    discount = calculate_discount(subtotal, order.get("customer_tier"))
    total = subtotal - discount
    return {
        "subtotal": format_currency(subtotal),
        "discount": format_currency(discount),
        "total": format_currency(total),
    }


def send_invoice(invoice, email):
    # TODO: conectar con el proveedor real de envio de correos
    print(f"Sending invoice to {email}: {invoice}")


if __name__ == "__main__":
    sample_order = {
        "items": [{"price": 10, "quantity": 2}],
        "customer_tier": "gold",
    }
    invoice = build_invoice(sample_order)
    send_invoice(invoice, "client@example.com")

client-dashboard/src/utils.py:

DISCOUNT_RATES = {
    "gold": 0.15,
    "silver": 0.10,
    "bronze": 0.05,
}


def calculate_discount(subtotal, tier):
    rate = DISCOUNT_RATES.get(tier, 0)
    return subtotal * rate


def format_currency(amount):
    # TODO: soportar monedas distintas a USD
    return f"${amount:.2f}"

client-dashboard/config/settings.yaml:

app:
  name: client-dashboard
  environment: production
  debug: false

database:
  host: db.internal
  port: 5432
  name: dashboard

logging:
  level: INFO

client-dashboard/docs/architecture.md:

# Arquitectura

El dashboard sigue una arquitectura de tres capas: `src/` para la logica de
negocio, `config/` para la configuracion por entorno y `tests/` para la
suite de pruebas.

## Componentes

- `app.py`: arma facturas y las envia.
- `utils.py`: calculo de descuentos y formato de moneda.

<!-- TODO: agregar diagrama de componentes -->

client-dashboard/notes.txt (esta es la más larga: son las notas de traspaso completas que te dejó quien se fue; pégalas tal cual, incluido el encabezado):

Notas de traspaso - client-dashboard
======================================

2026-02-03
Primera reunion con el equipo de soporte. El dashboard lo usan tres
personas del area de cuentas para revisar el estado de facturacion de
cada cliente antes de la llamada semanal.

2026-02-10
El calculo de descuento por nivel (gold/silver/bronze) vive en
src/utils.py. No hay validacion si el nivel no existe en el diccionario;
por ahora el default es 0, que es razonable pero no esta documentado
en ningun lado mas que en el propio codigo.

2026-02-17
El envio de factura por correo (src/app.py, send_invoice) todavia es un
print a consola. No hay integracion real con un proveedor de correo.
Antes de mandar esto a produccion alguien tiene que conectar eso con
el proveedor que use el equipo de plataforma.

2026-02-24
La configuracion vive en config/settings.yaml para el entorno de
produccion. Existe tambien config/settings.dev.yaml pero quedo vacio;
nadie llego a escribir la version de desarrollo.

2026-03-03
config/logging.conf tambien quedo vacio. El logging real todavia usa
los defaults de la libreria, no hay niveles por modulo configurados.

2026-03-10
Los tests (tests/test_app.py y tests/test_utils.py) son archivos
vacios. Se crearon como recordatorio de que faltan, pero nadie escribio
ni un solo caso todavia. Esto es lo primero que hay que atacar antes
de tocar la logica de descuentos.

2026-03-17
src/components/header.py y footer.py tambien estan vacios. La idea
original era separar el render del encabezado y el pie del dashboard,
pero el compañero que se fue nunca llego a esa parte.

2026-03-24
docs/setup.md quedo vacio. No hay instrucciones de instalacion mas
alla de lo que dice el README, que es generico.

2026-03-31
Reunion de cierre de sprint. Se decide congelar el desarrollo de
features nuevas hasta que alguien audite el estado real del proyecto:
que esta hecho, que esta a medias y que es solo un archivo vacio
esperando contenido.

2026-04-07
Nota personal: revisar si el .env.example refleja las variables que
realmente usa config/settings.yaml. A simple vista faltan al menos
dos: la URL de la base de datos y la clave del proveedor de correo.

2026-04-14
Segunda reunion con el equipo de soporte. Piden agregar un reporte de
clientes con descuento gold que superen cierto monto. Se anota como
pendiente, no se implementa todavia.

2026-04-21
El .gitignore existe pero nunca se reviso si excluye lo que hace falta
en un proyecto Python tipico: __pycache__, entornos virtuales,
archivos .env reales (no el .env.example, que si va versionado).

2026-04-28
Se detecta que docs/architecture.md describe tres capas (src, config,
tests) pero el diagrama que se menciona ahi nunca se dibujo. Queda
marcado en el propio documento.

2026-05-05
El compañero que llevaba este proyecto avisa que se va de la empresa
en dos semanas. A partir de aqui las notas se vuelven mas escuetas.

2026-05-12
Ultimo commit relevante antes de la salida: ajustes menores de
formato en src/app.py. No hay cambios de logica.

2026-05-19
Traspaso formal. Estas notas quedan como el unico registro de contexto
del proyecto para quien lo herede.

Fin de las notas.

Simular la antigüedad real del proyecto con touch -t

Ahora mismo los seis archivos que acabas de escribir —y los nueve que quedaron vacíos— tienen todos la fecha de hoy, porque fue el momento en que touch y nano los tocaron por última vez. Pero las notas que acabas de pegar cuentan una historia que empieza en febrero y termina en mayo. Para que la pregunta de la auditoría sobre "qué se modificó recientemente" tenga una respuesta con sentido, necesitas que las fechas del sistema de archivos coincidan con esa historia.

touch acepta la opción -t para fijar una fecha y hora exactas en lugar de "ahora". Su formato es [[CC]YY]MMDDhhmm: dos dígitos de siglo (opcionales), dos de año, dos de mes, dos de día, dos de hora y dos de minuto, todo pegado y sin separadores. Es una de las pocas opciones de touch que funciona idéntica en macOS y en Linux, así que no hay letra chica de por medio.

Ejecuta este bloque completo. Cada línea backdatea un archivo a la fecha en que las notas dicen que se tocó por última vez:

touch -t 202602030900 client-dashboard/.gitignore
touch -t 202602100900 client-dashboard/src/utils.py
touch -t 202602240900 client-dashboard/config/settings.dev.yaml
touch -t 202603030900 client-dashboard/config/logging.conf
touch -t 202603100900 client-dashboard/tests/test_app.py
touch -t 202603100900 client-dashboard/tests/test_utils.py
touch -t 202603170900 client-dashboard/src/components/header.py
touch -t 202603170900 client-dashboard/src/components/footer.py
touch -t 202603240900 client-dashboard/docs/setup.md
touch -t 202604070900 client-dashboard/.env.example
touch -t 202604280900 client-dashboard/docs/architecture.md
touch -t 202605120900 client-dashboard/src/app.py
touch -t 202605190900 client-dashboard/notes.txt

Dos archivos quedan deliberadamente sin tocar: README.md y config/settings.yaml. Se quedan con la fecha de hoy, como si alguien del equipo los hubiera actualizado la semana pasada, justo antes de que el proyecto llegara a tus manos. Eso es lo que vas a descubrir en la parte 3.

Parte 3 — La auditoría: cinco preguntas con evidencia

La regla del entregable es siempre la misma: pregunta, comando, resultado. No alcanza con correr un comando y mirarlo; tienes que poder señalar exactamente qué escribiste y qué obtuviste. Trabaja siempre parado en el directorio que contiene a client-dashboard (no dentro de él), para que las rutas de salida sean legibles.

Pregunta 1 — ¿Dónde están todos los archivos de configuración?

Primero define el criterio, por escrito, antes de correr nada: vas a considerar "configuración" cualquier archivo con extensión .yaml, .yml o .conf, o cuyo nombre empiece con .env. Fíjate en lo que no entra en esa definición: .gitignore no cuenta, porque configura a git, no a la aplicación, y git está fuera del alcance de esta guía.

find client-dashboard -iname "*.yaml" -o -iname "*.yml" -o -iname "*.conf" -o -iname ".env*"

Qué esperar:

client-dashboard/config/settings.yaml
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/.env.example

Interpretación: cuatro archivos, tres de ellos agrupados en config/ y uno suelto en la raíz. Nota que este comando no usa -type f: en este árbol funciona igual porque ninguna carpeta se llama *.yaml, pero si alguna vez mezclas -type f con varias condiciones unidas por -or vas a necesitar agruparlas entre paréntesis escapados (\( ... \)) para que la precedencia no te traicione. Lo vas a necesitar cuando esta guía te enseñe expresiones más largas; por ahora, con la lista de -iname sola alcanza.

Pregunta 2 — ¿Qué archivos se modificaron en los últimos siete días?

find client-dashboard -type f -mtime -7

Qué esperar:

client-dashboard/config/settings.yaml
client-dashboard/README.md

Y para confirmar que el resto realmente quedó afuera:

find client-dashboard -type f -mtime +7

Qué esperar:

client-dashboard/notes.txt
client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/architecture.md
client-dashboard/docs/setup.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py
client-dashboard/src/utils.py
client-dashboard/src/app.py

Interpretación: dos archivos recientes, trece con más de una semana. README.md y settings.yaml son justamente los dos que dejaste sin backdatear en la parte 2 — la evidencia coincide con la historia que armaste. En un proyecto heredado real, este resultado te diría algo concreto y accionable: alguien tocó la configuración de producción y el README la semana pasada, sin que quede registrado en ningún otro lado más que en esta fecha. Es la primera pregunta que le harías a tu equipo.

Pregunta 3 — ¿En qué líneas aparece la palabra TODO y cuántas hay?

Sin tuberías todavía, grep puede responder las dos mitades de esta pregunta con dos banderas distintas: -n te da las líneas exactas, -c te da un conteo por archivo.

grep -rn "TODO" client-dashboard

Qué esperar:

client-dashboard/docs/architecture.md:12:<!-- TODO: agregar diagrama de componentes -->
client-dashboard/src/utils.py:14:    # TODO: soportar monedas distintas a USD
client-dashboard/src/app.py:5:    # TODO: validar que order tenga los campos requeridos antes de procesar
client-dashboard/src/app.py:17:    # TODO: conectar con el proveedor real de envio de correos

Cuatro líneas, en tres archivos. Para el conteo por archivo:

grep -rc "TODO" client-dashboard

Qué esperar:

client-dashboard/notes.txt:0
client-dashboard/.gitignore:0
client-dashboard/.env.example:0
client-dashboard/README.md:0
client-dashboard/config/settings.yaml:0
client-dashboard/config/settings.dev.yaml:0
client-dashboard/config/logging.conf:0
client-dashboard/tests/test_utils.py:0
client-dashboard/docs/setup.md:0
client-dashboard/tests/test_app.py:0
client-dashboard/docs/architecture.md:1
client-dashboard/src/components/footer.py:0
client-dashboard/src/utils.py:1
client-dashboard/src/components/header.py:0
client-dashboard/src/app.py:2

Interpretación: grep -rc con -r imprime una línea por cada archivo que recorrió, incluidos los que dieron cero coincidencias —no solo los que matchearon—. Sin tuberías ni awk todavía, el total se suma a mano leyendo la columna de la derecha: 2 + 1 + 1 = 4, que coincide exactamente con las cuatro líneas que te mostró -n. Cuando el módulo 3 te enseñe | y el kit de texto, este mismo total vas a poder obtenerlo sin sumar nada tú mismo.

Pregunta 4 — ¿Cuál es el archivo más grande y cuántas líneas tiene?

Todavía no tienes sort ni tuberías para ordenar por tamaño de una sola vez, así que vas a apoyarte en algo que ya sabes hacer: acotar con -size, igual que hiciste en la lección 5. La técnica es ir subiendo el umbral hasta que quede un único archivo.

find client-dashboard -type f -size +500c

Qué esperar:

client-dashboard/notes.txt
client-dashboard/src/app.py

Todavía hay dos candidatos. Sube el umbral:

find client-dashboard -type f -size +1k

Qué esperar:

client-dashboard/notes.txt

Un solo archivo. Ahora mide sus líneas con la herramienta de la lección 4:

wc -l client-dashboard/notes.txt

Qué esperar:

      83 client-dashboard/notes.txt

Interpretación: notes.txt es el archivo más grande del proyecto, con 83 líneas. Fíjate en el criterio: usaste bytes exactos (c) en vez de kilobytes redondeados para el primer corte, precisamente para no depender de cómo cada sistema redondea las unidades. Esta narrowing por tamaño —empezar ancho, acotar de a un umbral por vez— es exactamente la misma disciplina que usaste con -name en la lección 5; solo cambia el filtro.

Pregunta 5 — ¿Qué archivos quedaron vacíos?

find client-dashboard -type f -size 0

Qué esperar:

client-dashboard/config/logging.conf
client-dashboard/config/settings.dev.yaml
client-dashboard/tests/test_utils.py
client-dashboard/tests/test_app.py
client-dashboard/docs/setup.md
client-dashboard/.gitignore
client-dashboard/.env.example
client-dashboard/src/components/header.py
client-dashboard/src/components/footer.py

Interpretación: nueve archivos, todos los que dejaste sin contenido en la parte 2. En un proyecto real, esta lista es oro: te dice exactamente qué es un archivo real y qué es apenas un recordatorio de "esto falta", sin necesidad de abrir ninguno de los quince archivos uno por uno.

El entregable: el informe de auditoría

Ahora sí, redacta el informe. Ábrelo con nano fuera de client-dashboard/ —por ejemplo en ~/practice/audit-client-dashboard.md— porque en el próximo paso vas a borrar todo el árbol de práctica, y el informe es justamente lo que quieres conservar.

nano ~/practice/audit-client-dashboard.md

El resultado debería verse así:

# Auditoría de client-dashboard

## 1. Archivos de configuración
Comando: find client-dashboard -iname "*.yaml" -o -iname "*.yml" -o -iname "*.conf" -o -iname ".env*"
Resultado: config/settings.yaml, config/logging.conf, config/settings.dev.yaml, .env.example (4 archivos)

## 2. Modificados en los últimos 7 días
Comando: find client-dashboard -type f -mtime -7
Resultado: config/settings.yaml, README.md (2 archivos; el resto tiene más de 7 días)

## 3. Ocurrencias de TODO
Comando: grep -rn "TODO" client-dashboard
Resultado: 4 líneas en 3 archivos (app.py: 2, utils.py: 1, architecture.md: 1)

## 4. Archivo más grande
Comando: find client-dashboard -type f -size +1k
Resultado: notes.txt, 83 líneas (wc -l)

## 5. Archivos vacíos
Comando: find client-dashboard -type f -size 0
Resultado: 9 archivos, incluidos ambos tests y ambos componentes de UI

Este documento es el verdadero entregable de la lección, no la terminal. Es lo que le mandas a tu equipo antes de tocar una sola línea de código.

Limpieza segura del árbol

Terminaste de practicar. Ahora aplica exactamente las tres reglas de seguridad de la lección 3, en orden.

Primero, lista lo que estás a punto de borrar y confirma que es lo que crees que es:

find client-dashboard

Revisa la salida: tienen que ser las mismas quince rutas de archivo más las seis carpetas que construiste en la parte 1, nada más. Recién ahora, con la lista verificada, borra:

rm -r client-dashboard

No hay comodín en ese comando —es un nombre literal, no un patrón—, así que la segunda regla de seguridad (no usar comodines a ciegas) queda satisfecha por construcción. Confirma que desapareció:

find client-dashboard

Qué esperar: find: client-dashboard: No such file or directory (o No such file or directory a secas, según tu sistema). Ese mensaje de error es la confirmación de que la limpieza funcionó. Tu audit-client-dashboard.md sigue intacto, un nivel más arriba.

Errores comunes

Confundir "cuántas veces aparece la palabra" con "cuántas líneas la contienen". grep -c cuenta líneas que matchean, no ocurrencias. Si una sola línea tuviera TODO TODO, grep -c la contaría como 1, no como 2. Es un error conceptual, no de sintaxis: nace de asumir que -c hace aritmética sobre el texto cuando en realidad clasifica líneas. Se detecta comparando la salida de -n (que muestra cada línea) contra la de -c (que las cuenta) en un archivo con más de una coincidencia por línea. Se corrige siendo explícito en tu informe sobre qué estás contando —líneas con TODO, no apariciones de TODO— en vez de asumir que ambas cosas son lo mismo. Esta guía no cubre la forma de contar ocurrencias exactas (grep -o seguido de un conteo), así que la definición honesta del criterio es la solución real, no un comando más avanzado.

Invertir el signo de -mtime. Es tentador leer -mtime -7 como "archivos con más de 7 días" porque el signo "menos" suena a "viejo". Es al revés: -7 significa menos de 7 días de diferencia (reciente), +7 significa más de 7 días (viejo). Se detecta cuando la lista de "recientes" te devuelve trece archivos y la de "viejos" te devuelve dos —exactamente al revés de lo esperado en este proyecto—. Se corrige recordando la regla: el signo describe la comparación matemática (menor que, mayor que), no una idea de "antigüedad negativa".

Una coma faltante en una llave anidada no da error, da un archivo con el nombre equivocado. Si en el comando de touch de la parte 1 escribes src/{app.pyutils.py} en vez de src/{app.py,utils.py}, bash no se queja: expande literalmente a un archivo llamado app.pyutils.py. No hay mensaje de error porque sintácticamente es válido, solo que no es lo que querías. Se detecta corriendo find client-dashboard -type f inmediatamente después de cada touch y contando: si esperabas quince archivos y ves catorce con uno de nombre raro, ahí está el problema. Se corrige borrando el archivo mal nombrado y revisando la coma antes de repetir el comando.

Ejercicios

Cada ejercicio usa exactamente las herramientas de esta lección sobre un caso distinto al de client-dashboard. Intenta cada uno antes de abrir la solución.

Ejercicio 1: reconstruye este árbol con el mínimo de comandos

Tienes este mapa de un proyecto llamado blog-engine:

blog-engine/
├── README.md
├── posts/
│   ├── 2026-01-10-hello-world.md
│   └── drafts/
├── templates/
│   ├── base.html
│   └── post.html
└── static/
    ├── css/
    │   └── style.css
    └── img/

Escribe los comandos (uno para carpetas, uno para archivos) que lo reconstruyen completo, incluidas las dos carpetas que quedan vacías (posts/drafts/ y static/img/).

Ver solución
mkdir -p blog-engine/{posts/drafts,templates,static/css,static/img}
touch blog-engine/{README.md,posts/2026-01-10-hello-world.md,templates/{base.html,post.html},static/css/style.css}

Por qué funciona: el primer comando expande a cuatro rutas de carpeta; posts/drafts crea de paso posts gracias a -p, e igual pasa con static/css y static/img respecto de static. El segundo comando coloca los cinco archivos del mapa usando llaves anidadas para templates/{base.html,post.html}; como posts/drafts y static/img no aparecen en la llave de touch, quedan como carpetas vacías, tal como pide el mapa.

Ejercicio 2: corrige la interpretación de un compañero

Un compañero corre esto sobre un proyecto llamado inventory y concluye: "-mtime -7 me muestra los archivos viejos, los que nadie tocó en la última semana."

$ find inventory -type f -mtime -7
inventory/receiving.py
inventory/config.yaml

¿Está en lo correcto? Si no, ¿qué significa realmente ese resultado?

Ver solución

Está invertido. -mtime -7 selecciona archivos cuya modificación ocurrió hace menos de 7 días —es decir, los recientes—, no los viejos. Los dos archivos que aparecieron (receiving.py y config.yaml) son los que alguien tocó en la última semana; todo lo demás en inventory tiene más de siete días de antigüedad. Para ver la lista de los viejos, el comando correcto sería find inventory -type f -mtime +7.

Por qué funciona: el signo en -mtime es una comparación matemática sobre la diferencia en días (menor que n, mayor que n), no una etiqueta de "antigüedad negativa". Memorizar la regla en esos términos evita la confusión.

Ejercicio 3: suma sin tuberías

Corriste esto sobre un proyecto llamado inventory buscando la palabra FIXME:

inventory/receiving.py:3
inventory/shipping.py:0
inventory/reports/monthly.py:2
inventory/reports/weekly.py:0
inventory/utils.py:1

Sin usar | ni awk (todavía fuera de alcance), responde: ¿cuántas líneas con FIXME hay en total en el proyecto? ¿En cuántos archivos distintos aparece al menos una? ¿Cuál archivo concentra más?

Ver solución
  • Total de líneas con FIXME: sumas la columna de la derecha a mano: 3 + 0 + 2 + 0 + 1 = 6.
  • Archivos con al menos una coincidencia: los que tienen un número mayor a 0: receiving.py, monthly.py y utils.py3 archivos.
  • El que concentra más: receiving.py, con 3.

Por qué funciona: grep -rc te da un conteo por archivo, incluidos los ceros; sin tuberías para sumar automáticamente, la lectura manual de esa columna es la única vía disponible con lo que sabes hasta este módulo, y es exactamente el mismo método que usaste en la pregunta 3 del proyecto.

Ejercicio 4: aísla el archivo más grande en un solo filtro

Un directorio data/ tiene estos cinco archivos y tamaños:

ArchivoTamaño
raw_export.csv45,000 bytes
log.txt12,000 bytes
summary.txt800 bytes
notes.md120 bytes
config.json95 bytes

Escribe un único comando find -size que devuelva exclusivamente raw_export.csv, y explica por qué elegiste ese umbral y esa unidad.

Ver solución
find data -type f -size +20000c

Por qué funciona: el umbral tiene que caer estrictamente entre el segundo archivo más grande (log.txt, 12,000 bytes) y el más grande (raw_export.csv, 45,000 bytes); 20,000 cumple esa condición con margen de sobra. Se usa el sufijo c (bytes exactos) en vez de k para no depender de cómo cada sistema redondea kilobytes al comparar: con bytes exactos el resultado es idéntico en cualquier máquina.

Resumen y siguiente paso

Antes de avanzar deberías poder:

  • Reconstruir un árbol de directorios completo a partir de un mapa, con el mínimo de comandos, combinando llaves anidadas en mkdir -p y touch.
  • Definir por escrito el criterio de una búsqueda (qué cuenta como "configuración", qué cuenta como "reciente") antes de correr el comando que la resuelve.
  • Combinar find y grep para responder preguntas concretas sobre un proyecto que no conoces, con evidencia reproducible en vez de impresiones.
  • Aplicar la regla de "listar antes de borrar" de la lección 3 como cierre obligatorio de cualquier práctica, no como un paso opcional.

Este módulo empezó con la promesa de que podrías crear estructura, leer archivos que no caben en pantalla y encontrar una aguja en un pajar de miles de archivos. Lo que acabas de hacer es exactamente eso, junta y sobre un caso con forma de trabajo real: reconstruiste, llenaste, mediste, buscaste y limpiaste, todo con evidencia en cada paso.

También tocaste el límite honesto de lo que sabes hasta hoy. Sumaste una columna de números a mano porque no tenías con qué sumarla sola; acotaste el tamaño de un archivo probando umbrales en vez de pedirle a la máquina que ordenara la lista por ti. Ese esfuerzo manual no fue un defecto de esta lección: es la motivación exacta del módulo 3, donde vas a conectar find, grep, sort y wc entre sí con | y vas a resolver estas mismas cinco preguntas en una fracción de las pulsaciones que usaste hoy.

Recursos