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:
$PROJECT_DIR kotlin check-library-layout.main.kts $PROJECT_DIRkotlin check-library-layout.main.kts .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.
val expectedFiles = listOf("README.md", "LICENSE", "CODE_OF_CONDUCT.md")
val separator = System.lineSeparator() + "- "
println(
expectedFiles.joinToString(
separator,
prefix = "Expected files:$separator"
)
)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.
-
declara una referencia que no puede reasignarse. Después de definirvalval, ese nombre no puede apuntar a otra lista dentro del mismo alcance.expectedFilesexpectedFiles - 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,
crea una lista de solo lectura, adecuada para expresar que el criterio del script no cambia durante esta ejecución.listOf()listOf() - Kotlin también ofrece
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.mutableListOf()mutableListOf() -
obtiene el salto de línea recomendado por el sistema operativo. Luego, el operadorSystem.lineSeparator()System.lineSeparator()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$$inserta el contenido de"Expected files:$separator""Expected files:$separator"después del título.separatorseparator -
construye un texto a partir de los elementos de una colección. En este caso, usajoinToString()joinToString()entre cada archivo yseparatorseparatorantes del primer elemento.prefixprefix
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.
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.
-
contiene los argumentos recibidos al ejecutar el script. Por ejemplo, si ejecutamosargsargskotlin check-library-layout.main.kts ., el punto.queda disponible como el primer argumento. -
obtiene el primer argumento. En este caso lo guardamos enargs[0]args[0]porque representa la ruta del proyecto que queremos revisar.projectPathprojectPath -
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.require(args.isNotEmpty())require(args.isNotEmpty()) - El bloque
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.{ "Missing required argument: <project-path>" }{ "Missing required argument: <project-path>" } - 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 evita que el script intente leer require()require() 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.
args[0]args[0]
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.
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.
-
declara una función. En este caso,funfunrecibe dos parámetros: la ruta base del proyecto y una ruta relativa que queremos revisar.checkPath()checkPath() - Los parámetros se escriben con la forma
. Por esonombre: Tiponombre: Tipoindica queprojectDirectory: PathprojectDirectory: Pathdebe ser una ruta, mientras queprojectDirectoryprojectDirectoryindica querelativePath: StringrelativePath: Stringes texto.relativePathrelativePath -
representa una ruta del sistema de archivos. En el fragmento,PathPathconvierte el texto recibido por argumento en una ruta que el script puede usar para construir otras rutas.Path.of(projectPath)Path.of(projectPath) -
construye una ruta dentro del directorio del proyecto. Por ejemplo, si el proyecto esprojectDirectory.resolve(relativePath)projectDirectory.resolve(relativePath).y la ruta relativa esREADME.md, el script revisará el archivo./README.md. -
pregunta si esa ruta existe. El resultado se usa en unFiles.exists(path)Files.exists(path)para imprimirififFoundcuando la ruta está presente oMissingcuando no lo está. -
repite una acción por cada elemento de la listafor (expectedFile in expectedFiles)for (expectedFile in expectedFiles). En cada vuelta, el valor actual queda disponible con el nombreexpectedFilesexpectedFiles.expectedFileexpectedFile - La llamada
reutiliza la misma función para cada archivo esperado. Así evitamos escribir varias veces la misma estructura decheckPath(projectDirectory, expectedFile)checkPath(projectDirectory, expectedFile).ifif
La función nombra una responsabilidad
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.
checkPath()checkPath()
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
antes de usarlo evita errores confusos; y extraer
argsargs permite nombrar una responsabilidad repetida sin convertir el script
en una abstracción innecesariamente grande.
checkPath()checkPath()
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?
Referencias recomendadas
- “ Get started with Kotlin custom scripting – tutorial ” en Kotlin docs por JetBrainsTutorial oficial de JetBrains que enseña a construir scripts de Kotlin ejecutables con punto de entrada explícito y manejo de dependencias Maven. Cubre la definición del tipo de script, la validación de argumentos, y la creación de un host que compila y ejecuta scripts con un contrato operativo claro.