Saltar al contenido principal

Scripts de apoyo como software reusable

Metadatos de la lección

Autoría:
Ignacio Slater-Muñoz
Última actualización:
8 de mayo de 2026

Cambios recientes:

  • c8c458e · 8 de mayo de 2026 · 💥 rm(bibliography): Remove unused bibliography source files ( GitLab / GitHub )
  • 6e98bbc · 8 de mayo de 2026 · 🎯 feat(lessons): Add file existence check to project layout script ( GitLab / GitHub )
  • 76228da · 8 de mayo de 2026 · 💥 rm(lessons): Simplify project by removing unused lesson artefacts ( GitLab / GitHub )

Abstract

En el desarrollo de bibliotecas, no todo el software reusable aparece como una API publicada. También existen herramientas pequeñas que ayudan a sostener el trabajo del proyecto: scripts que revisan archivos, preparan datos, ejecutan comprobaciones o automatizan pasos repetidos. Esta lección abre una unidad orientada a ese plano operativo: el trabajo de scripting que sostiene el proyecto desde adentro.

En esta lección construiremos un primer script de apoyo en Kotlin. El objetivo será revisar si un proyecto contiene archivos esperados para una biblioteca README.md, LICENSE y CODE_OF_CONDUCT.md, los mismos que identificamos como condición mínima para que una biblioteca sea adoptable. A partir de ese ejemplo, estudiaremos cómo agrupar datos en una lista, leer argumentos de la terminal, validar precondiciones, representar rutas del sistema de archivos y extraer una función pequeña para evitar repetir lógica.

Más que producir un script complejo, la lección busca mostrar una idea de diseño: incluso una herramienta local puede tener un contrato operativo claro. Ese contrato indica cómo se ejecuta, qué entradas espera, qué salida produce y cómo comunica sus resultados.

Script no significa desechable

Una biblioteca se diseña para que otros programas la integren mediante una API. Una aplicación se diseña para una experiencia de uso final. Un script, en cambio, suele nacer con una localidad muy concreta dentro del proyecto: revisar archivos, preparar datos, generar reportes o repetir una secuencia de comandos. Ese carácter local no lo vuelve menos importante ni lo convierte automáticamente en algo desechable.

No todos los scripts cumplen el mismo rol. Algunos sirven para explorar una idea y se descartan al terminar. Otros guardan una secuencia puntual de pasos. Otros, en cambio, empiezan a sostener una práctica recurrente del proyecto.

  • Script exploratorio: ayuda a descubrir algo. Su valor está en aprender rápido, no en dejar una forma de uso estable.
  • Script de una sola vez: captura una operación puntual. Puede ser claro, pero no necesita prometer mantenimiento.
  • Script de apoyo reusable: automatiza una tarea que volverá a aparecer y que otras personas o procesos podrían ejecutar.

Contrato operativo

El contrato operativo de un script describe cómo se ejecuta, qué entradas acepta, qué salida produce, cómo informa fallas y qué condiciones del entorno necesita.

En esta lección, ese contrato será pequeño: el script recibirá un directorio de proyecto, revisará si contiene algunos archivos esperados para una biblioteca y mostrará un informe de comprobación.

Cómo ejecutar el script

Antes de revisar la sintaxis de Kotlin, conviene ubicar el script en su contexto de uso. El archivo se llamará check-library-layout.main.kts y estará pensado para ejecutarse desde la terminal.

En esta lección asumiremos que el comando kotlin está disponible en el entorno. Para ejecutar el script sobre un proyecto, usaremos:

Ejecutar el script sobre $PROJECT_DIR
kotlin check-library-layout.main.kts $PROJECT_DIR
Por ejemplo, kotlin check-library-layout.main.kts . ejecuta el script sobre la ubicación actual.

Agrupar archivos esperados en una lista

Si el script debe revisar varios archivos, conviene agrupar esos nombres en una sola colección. Así el código expresa mejor la intención: no estamos trabajando con datos aislados, sino con el conjunto de archivos que este proyecto espera encontrar en una biblioteca reusable.

En este fragmento todavía no revisaremos si los archivos existen. Primero construiremos una lista de archivos esperados y la convertiremos en un mensaje legible para la terminal.

Fragmento 1: lista de archivos esperados
scripts/check-library-layout.main.kts
val expectedFiles = listOf("README.md", "LICENSE", "CODE_OF_CONDUCT.md")
val separator = System.lineSeparator() + "- "

println(
    expectedFiles.joinToString(
        separator,
        prefix = "Expected files:$separator"
    )
)
Salida del fragmento 1
Expected files:
- README.md
- LICENSE
- CODE_OF_CONDUCT.md

Detalles clave

Este fragmento introduce una idea de diseño y varias piezas pequeñas de sintaxis. La idea de diseño es simple: cuando varios valores representan una misma categoría, conviene agruparlos y darles un nombre.

  • val declara una referencia que no puede reasignarse. Después de definir expectedFiles, ese nombre no puede apuntar a otra lista dentro del mismo alcance.
  • Esa inmutabilidad corresponde a la referencia, no necesariamente a una inmutabilidad profunda de todo lo que podría estar contenido en un objeto. En este caso, listOf() crea una lista de solo lectura, adecuada para expresar que el criterio del script no cambia durante esta ejecución.
  • Kotlin también ofrece mutableListOf() para crear listas modificables. No la usamos aquí porque no queremos agregar ni quitar archivos esperados mientras el script corre: queremos declarar el criterio inicial y mantenerlo estable.
  • System.lineSeparator() obtiene el salto de línea recomendado por el sistema operativo. Luego, el operador + concatena ese salto de línea con "- " para formar el separador que aparecerá antes de cada archivo.
  • El símbolo $ permite interpolar valores dentro de un texto. Por eso "Expected files:$separator" inserta el contenido de separator después del título.
  • joinToString() construye un texto a partir de los elementos de una colección. En este caso, usa separator entre cada archivo y prefix antes del primer elemento.

Recibir la ruta del proyecto como argumento

Hasta ahora, el script podía mostrar información fija. El siguiente paso es hacerlo un poco más reusable: en vez de asumir qué proyecto debe revisar, recibirá la ruta del proyecto desde la terminal.

Esa ruta pasa a formar parte del contrato operativo del script. Quien lo ejecuta debe indicar qué directorio quiere revisar, y el script debe comprobar que recibió ese dato antes de continuar.

Fragmento 2: leer un argumento obligatorio
scripts/check-library-layout.main.kts
require(args.isNotEmpty()) { "Missing required argument: <project-path>" }
val projectPath = args[0]
println("Project path: $projectPath")

Detalles clave

Este fragmento introduce dos ideas importantes para scripts de apoyo: leer argumentos de la terminal y validar una precondición antes de usar esos argumentos.

  • args contiene los argumentos recibidos al ejecutar el script. Por ejemplo, si ejecutamos kotlin check-library-layout.main.kts ., el punto . queda disponible como el primer argumento.
  • args[0] obtiene el primer argumento. En este caso lo guardamos en projectPath porque representa la ruta del proyecto que queremos revisar.
  • require(args.isNotEmpty()) valida que exista al menos un argumento. Si la condición es verdadera, el script continúa. Si es falsa, Kotlin detiene la ejecución con un error que incluye el mensaje indicado.
  • El bloque { "Missing required argument: <project-path>" } es una lambda: una función pequeña escrita directamente donde se necesita. Aquí se usa para producir el mensaje de error solo si la validación falla.
  • Esta forma de escribir la lambda después de los paréntesis se llama trailing lambda. En Kotlin, cuando el último argumento de una función es otra función, esa lambda puede escribirse fuera de los paréntesis. Es la misma idea de pasar una función como argumento, pero con una sintaxis más legible.

Validar antes de usar

La línea con require() evita que el script intente leer args[0] cuando no se entregó ningún argumento. Esa es una regla pequeña, pero muy importante para scripts reutilizables: primero se comprueba el contrato de entrada, después se trabaja con los datos.

Comprobar rutas esperadas dentro del proyecto

Una vez que el script conoce la ruta del proyecto y tiene una lista de archivos esperados, puede comenzar a revisar el sistema de archivos. Para eso necesita convertir la ruta recibida como texto en una ruta que Kotlin pueda manipular y repetir la misma comprobación para cada archivo esperado.

Este fragmento introduce una primera función del script. La función no representa todavía una abstracción compleja: solo le da nombre a una operación que se repite, revisar si una ruta existe dentro del proyecto.

Fragmento 3: revisar cada ruta esperada
scripts/check-library-layout.main.kts
import java.nio.file.Files
import java.nio.file.Path
// ...
fun checkPath(projectDirectory: Path, relativePath: String) {
    val path = projectDirectory.resolve(relativePath)
    if (Files.exists(path)) {
        println("Found: $relativePath")
    } else {
        println("Missing: $relativePath")
    }
}

val projectDirectory: Path = Path.of(projectPath)
for (expectedFile in expectedFiles) {
    checkPath(projectDirectory, expectedFile)
}

Detalles clave

Este fragmento combina tres ideas: declarar una función, representar rutas del sistema de archivos y repetir una acción para cada elemento de una lista.

  • fun declara una función. En este caso, checkPath() recibe dos parámetros: la ruta base del proyecto y una ruta relativa que queremos revisar.
  • Los parámetros se escriben con la forma nombre: Tipo. Por eso projectDirectory: Path indica que projectDirectory debe ser una ruta, mientras que relativePath: String indica que relativePath es texto.
  • Path representa una ruta del sistema de archivos. En el fragmento, Path.of(projectPath) convierte el texto recibido por argumento en una ruta que el script puede usar para construir otras rutas.
  • projectDirectory.resolve(relativePath) construye una ruta dentro del directorio del proyecto. Por ejemplo, si el proyecto es . y la ruta relativa es README.md, el script revisará el archivo ./README.md.
  • Files.exists(path) pregunta si esa ruta existe. El resultado se usa en un if para imprimir Found cuando la ruta está presente o Missing cuando no lo está.
  • for (expectedFile in expectedFiles) repite una acción por cada elemento de la lista expectedFiles. En cada vuelta, el valor actual queda disponible con el nombre expectedFile.
  • La llamada checkPath(projectDirectory, expectedFile) reutiliza la misma función para cada archivo esperado. Así evitamos escribir varias veces la misma estructura de if.

La función nombra una responsabilidad

checkPath() no se extrae porque el script sea grande, sino porque la operación tiene una responsabilidad clara: recibir una ruta base, combinarla con una ruta relativa y comunicar si existe. Esa separación hace que el script sea más fácil de leer y cambiar.

Conclusiones

Un script de apoyo puede parecer una pieza menor dentro de un proyecto, pero cuando automatiza una tarea recurrente empieza a formar parte de la infraestructura cotidiana de la biblioteca. Por eso conviene diseñarlo con los mismos criterios básicos que aplicaríamos a otros artefactos reusables: entradas explícitas, responsabilidades claras y una salida comprensible.

En el ejemplo de esta lección, el script no modifica el proyecto ni intenta resolver todo el proceso de publicación de una biblioteca. Su responsabilidad es más acotada: recibir una ruta, revisar algunos archivos esperados y comunicar qué encontró. Esa simplicidad es una ventaja, porque permite reconocer con claridad el contrato operativo del script.

También vimos que las decisiones pequeñas de implementación tienen impacto en la legibilidad. Agrupar los archivos esperados en una lista expresa que forman parte de un mismo criterio; validar args antes de usarlo evita errores confusos; y extraer checkPath() permite nombrar una responsabilidad repetida sin convertir el script en una abstracción innecesariamente grande.

Puntos clave

  • Un script de apoyo reusable automatiza una tarea que puede repetirse durante el desarrollo, la revisión local o la integración continua.
  • El contrato operativo de un script describe cómo se ejecuta, qué entradas acepta, qué salida produce y cómo comunica sus fallas.
  • Agrupar valores relacionados en una colección ayuda a expresar la intención del código y evita tratar datos de una misma categoría como elementos aislados.
  • Validar las entradas antes de usarlas hace que los errores aparezcan cerca de su causa y con mensajes más comprensibles.
  • Las rutas del sistema de archivos deben tratarse como rutas, no solo como texto, para construir comprobaciones más claras y portables.
  • Una función pequeña puede mejorar la lectura del script cuando nombra una responsabilidad concreta y evita duplicar una misma operación.

Reflexión de cierre

Antes de agregar más comportamiento a un script, conviene preguntarse qué parte de su contrato ya está clara y cuál todavía depende de supuestos implícitos. Pero también conviene preguntarse qué ocurre cuando ese script empieza a ejecutarse en más contextos, por más personas o en procesos automáticos. En ese punto, las preguntas cambian: ya no se trata solo de cómo ejecutarlo, sino de cómo mantenerlo, cómo asegurarse de que sigue cumpliendo su contrato y cómo hacer ese contrato reconocible desde afuera.

¿Con ganas de más?