Proyecto práctico: Aplicación de consola con persistencia JSON
Antes: Concurrencia y canales. Después: Proyecto web completo. Referencia técnica: Línea de comandos (CLI), Estructura de proyectos.
¿Qué vas a construir aquí?
Una aplicación de consola (CLI) es un programa que se ejecuta directamente en la terminal sin interfaz gráfica. Es el tipo de software utilizado en automatizaciones, procesamiento de datos por lotes (batch processing), scripts de mantenimiento de servidores y utilidades para desarrolladores.
En este tutorial guiado construiremos un gestor de compras e inventario:
1. Estructura una lista de artículos con cantidades y precios decimales exactos.
2. Guarda los datos en un archivo físico en el disco (compras.json) en formato JSON estructurado.
3. Lee el archivo del disco, valida su integridad sintáctica (json_verify) y reconstruye las estructuras de datos en memoria (json_decode).
4. Procesa y totaliza los importes con precisión decimal financiera usando funciones modulares.
5. Gestiona posibles fallos de lectura o escritura de forma robusta.
1. Preparar el entorno de trabajo
Abre tu terminal y crea un directorio limpio para este proyecto:
mkdir proyecto-compras
cd proyecto-compras
Abre tu editor y crea un archivo llamado main.joss.
2. El código completo del programa
Escribe o copia el siguiente programa completo dentro de main.joss:
public func totalizar(array $compras): decimal {
decimal $total = 0m
foreach ($compras as $compra) {
$total = $total + decimal($compra["precio"]) * intval($compra["cantidad"])
}
return $total
}
$compras = [
{"nombre": "Cuaderno", "precio": "12.50", "cantidad": 2},
{"nombre": "Lapiz", "precio": "3.50", "cantidad": 5}
]
$guardado = file_put_contents("compras.json", json_encode($compras))
$guardado ? {} : { throw "No se pudo guardar compras.json" }
$texto = file_get_contents("compras.json")
$texto == null ? { throw "No se pudo leer compras.json" } : {}
json_verify($texto) ? {} : { throw "El archivo no contiene JSON valido" }
$leidas = json_decode($texto)
print("Articulos: " . count($leidas))
print("Total: " . totalizar($leidas))
print("Archivo guardado")
3. Explicación paso a paso de la arquitectura
Analicemos cómo interactúan los distintos subsistemas del lenguaje en este programa:
1. La función de cálculo (totalizar)
public func totalizar(array $compras): decimal {
return 0m
}
- Recibe un
arrayde compras y promete retornar un valor de tipodecimal. - Inicializa un acumulador exacto:
decimal $total = 0m. - Recorre cada elemento con
foreach ($compras as $compra). - Extrae
"precio"y"cantidad"usando claves de mapa. Observa cómo convertimos explícitamente:decimal($compra["precio"]): Convierte el texto numérico a decimal de coma fija.intval($compra["cantidad"]): Convierte la cantidad a entero de 64 bits.
- Multiplica ambos valores y los suma a
$total.
2. Estructura de datos en memoria
$compras = [
{"nombre": "Cuaderno", "precio": "12.50", "cantidad": 2},
{"nombre": "Lapiz", "precio": "3.50", "cantidad": 5}
]
- Definimos un array cuyos elementos son mapas asociativos (
{"clave": valor}). - Guardar los precios como texto (
"12.50") dentro del JSON es una buena práctica contable: evita que los decodificadores JSON estándar introduzcan imprecisiones binarias al leer números flotantes.
3. Persistencia en disco con JSON
$guardado = file_put_contents("compras.json", json_encode($compras))
$guardado ? {} : { throw "No se pudo guardar compras.json" }
json_encode($compras): Transforma la estructura en memoria de Joss a un texto estándar JSON.file_put_contents("compras.json", ...): Escribe ese texto en el archivo físico en el disco duro. Devuelvetruesi tuvo éxito ofalsesi falló por permisos o falta de espacio.- La expresión ternaria actúa como salvaguarda: si
$guardadoes falso, lanza un error conthrow.
4. Lectura y validación de seguridad
$texto = file_get_contents("compras.json")
$texto == null ? { throw "No se pudo leer compras.json" } : {}
json_verify($texto) ? {} : { throw "El archivo no contiene JSON valido" }
file_get_contents(...): Recupera los bytes del archivo en una cadena de texto. Si el archivo no existe, devuelvenull.json_verify($texto): Función nativa de Joss que comprueba si una cadena cumple con la especificación JSON válida sin llegar a parsear todo el árbol a memoria. Si el archivo está corrupto o fue editado incorrectamente por un usuario, lo detecta de inmediato.json_decode($texto): Reconstruye los datos JSON en arrays y maps nativos de Joss listos para ser procesados.
4. Análisis y Ejecución
Primero, ejecutemos el análisis estático para garantizar que no haya inconsistencias:
joss analyze main.joss
Si todo es correcto, ejecuta el programa:
joss run main.joss
Salida producida en consola:
Articulos: 2
Total: 42.5
Archivo guardado
Si revisas tu carpeta con ls o el explorador de archivos, verás que se ha creado el archivo físico compras.json. Si lo abres, verás:
[{"cantidad":2,"nombre":"Cuaderno","precio":"12.50"},{"cantidad":5,"nombre":"Lapiz","precio":"3.50"}]
5. Salida visual con colores: El módulo nativo Console
Para que tu aplicación de consola ofrezca una experiencia visual atractiva y profesional, puedes colorear y destacar los mensajes en la terminal mediante la clase nativa Console:
print(Console::green("✓ Archivo guardado con éxito"))
print(Console::yellow("⚠ Advertencia: El stock es bajo"))
print(Console::red("✗ Error al procesar datos"))
print(Console::bold("Total a pagar: S/ 42.50"))
Métodos disponibles de Console:
| Método | Propósito y Color | Uso típico |
|---|---|---|
Console::green($t) |
Verde | Operaciones exitosas, confirmaciones (✓ OK). |
Console::red($t) |
Rojo | Errores, fallos de validación, excepciones. |
Console::yellow($t) |
Amarillo | Advertencias, avisos que requieren atención. |
Console::blue($t) / Console::cyan($t) |
Azul / Cian | Títulos, enlaces, información descriptiva. |
Console::bold($t) |
Negrita | Totales numéricos, nombres destacados. |
Console::clear() |
Limpiar pantalla | Reinicia la terminal antes de mostrar un menú. |
6. Ejercicios para expandir el proyecto
- Añadir artículos dinámicamente:
- Modifica el programa para pedirle al usuario el nombre, precio y cantidad del siguiente producto usando
cin >> $nombre. - Agrégalo al array con
$compras[] = ...antes de guardar el archivo.
- Modifica el programa para pedirle al usuario el nombre, precio y cantidad del siguiente producto usando
- Filtrar artículos caros:
- Crea una función
public func articulosCaros(array $compras, decimal $umbral): arrayque retorne un nuevo array con solo aquellos productos cuyo precio supere el umbral.
- Crea una función
- Colorear el reporte final:
- Usa
Console::green(...)para mostrar"Archivo guardado"yConsole::bold(...)para el total.
- Usa
Siguiente paso
Ahora que has dominado la persistencia de datos en disco y el desarrollo de utilidades de consola, es hora de explorar el área más fuerte de Joss: el desarrollo de aplicaciones web de alto rendimiento con rutas, controladores, bases de datos y vistas HTML.
Continúa con: Construir una aplicación web completa con el stack nativo.