Las seis lecciones anteriores te han dado piezas sueltas: la estructura de un fichero Java, las variables y sus tipos, los operadores y la entrada/salida por consola. Esta lección las monta todas juntas en el primer programa real del curso: BiblioTechApp, la aplicación que Nexus Software usará para registrar la devolución de un libro de su biblioteca técnica y emitir el recibo con la multa correspondiente. No lo escribiremos de golpe: lo construiremos en cuatro versiones sucesivas, cada una mejorando la anterior, que es exactamente como se desarrolla software en la vida real. Al final analizaremos con honestidad qué no sabe hacer todavía y qué módulo del curso resolverá cada carencia.
Contenido
- La especificación del programa
- Versión 1: el esqueleto con datos fijos
- Versión 2: pedir los datos al usuario
- Versión 3: cálculos correctos con el operador ternario
- Versión 4: la salida formateada y el tope de multa
- El código completo final, comentado
- Compilar y ejecutar
BiblioTechApp - Juegos de datos de prueba y salida esperada
- Las limitaciones del programa y cómo se resolverán
- Errores Comunes y Consejos
- Ejercicios
- La especificación del programa
Antes de escribir una línea, conviene fijar qué debe hacer el programa. Esto es lo primero que se hace en cualquier proyecto profesional, y ahorra muchísimo trabajo.
Nombre: BiblioTechApp
Paquete: com.nexussoftware.bibliotech
Comportamiento esperado:
- Mostrar una cabecera de bienvenida con el nombre de la empresa y de la aplicación.
- Pedir por consola los datos de la devolución de un préstamo:
- Nombre del empleado
- Título del libro
- ISBN
- Días transcurridos desde que se prestó
- Calcular:
- Los días de retraso, que nunca pueden ser negativos.
- El retraso expresado en semanas completas y días sueltos.
- La multa: días de retraso multiplicados por la tarifa diaria.
- El tope: la multa nunca puede superar el máximo establecido.
- El estado del préstamo: en plazo o con retraso.
- Mostrar un recibo formateado y legible con todos esos datos.
Reglas de negocio de la biblioteca de Nexus Software:
| Regla | Valor | Constante |
|---|---|---|
| Duración estándar del préstamo | 15 días | DIAS_PRESTAMO |
| Tarifa por día de retraso | 0,25 € | TARIFA_DIARIA |
| Multa máxima por préstamo | 20,00 € | MULTA_MAXIMA |
| Umbral de retraso leve | 7 días | UMBRAL_LEVE |
Restricciones técnicas de esta versión: un único método main, sin condicionales, sin bucles, sin clases propias. Todo lo que necesitas ya lo has aprendido.
flowchart TD
A["Mostrar cabecera"] --> B["Pedir empleado, titulo,<br/>ISBN y dias transcurridos"]
B --> C["Calcular dias de retraso<br/>(saturados a cero)"]
C --> D["Descomponer en<br/>semanas y dias"]
D --> E["Calcular multa base<br/>= retraso x tarifa"]
E --> F["Aplicar tope<br/>MULTA_MAXIMA"]
F --> G["Determinar estado<br/>y gravedad"]
G --> H["Imprimir recibo<br/>formateado con printf"]
- Versión 1: el esqueleto con datos fijos
La primera versión no pide nada: los datos están escritos en el código. Sirve para validar la estructura y los cálculos sin la complicación de la entrada.
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final String NOMBRE_EMPRESA = "Nexus Software";
final int DIAS_PRESTAMO = 15;
final double TARIFA_DIARIA = 0.25;
String empleado = "Marta Ruiz";
String titulo = "Java Efectivo";
String isbn = "978-0000000001";
int diasTranscurridos = 27;
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO;
double multa = diasRetraso * TARIFA_DIARIA;
System.out.println("=== BiblioTech - " + NOMBRE_EMPRESA + " ===");
System.out.println("Empleado: " + empleado);
System.out.println("Libro: " + titulo);
System.out.println("ISBN: " + isbn);
System.out.println("Dias de retraso: " + diasRetraso);
System.out.println("Multa: " + multa + " EUR");
}
}Salida:
=== BiblioTech - Nexus Software === Empleado: Marta Ruiz Libro: Java Efectivo ISBN: 978-0000000001 Dias de retraso: 12 Multa: 3.0 EUR
Funciona, pero tiene tres defectos evidentes que iremos corrigiendo:
- Los datos están fijos. Para procesar otra devolución hay que editar el código y recompilar.
- Los cálculos son ingenuos. Si el libro se devuelve a los 9 días,
diasRetrasovaldría-6y la multa-1.5 EUR: la biblioteca pagando al empleado. - La salida es pobre.
3.0 EURno es un importe presentable, y las etiquetas no están alineadas.
- Versión 2: pedir los datos al usuario
Sustituimos los valores fijos por lectura con Scanner, aplicando la regla de la lección anterior: todo se lee con nextLine() y se convierte con Integer.parseInt cuando hace falta.
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
Scanner entrada = new Scanner(System.in);
final String NOMBRE_EMPRESA = "Nexus Software";
final int DIAS_PRESTAMO = 15;
final double TARIFA_DIARIA = 0.25;
System.out.println("=== BiblioTech - " + NOMBRE_EMPRESA + " ===");
System.out.println("Registro de devolucion de prestamo");
System.out.println();
System.out.print("Empleado : ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro : ");
String titulo = entrada.nextLine();
System.out.print("ISBN : ");
String isbn = entrada.nextLine();
System.out.print("Dias transcurridos : ");
int diasTranscurridos = Integer.parseInt(entrada.nextLine());
int diasRetraso = diasTranscurridos - DIAS_PRESTAMO;
double multa = diasRetraso * TARIFA_DIARIA;
System.out.println();
System.out.println("Empleado: " + empleado);
System.out.println("Libro: " + titulo);
System.out.println("ISBN: " + isbn);
System.out.println("Dias de retraso: " + diasRetraso);
System.out.println("Multa: " + multa + " EUR");
entrada.close();
}
}Ya es un programa útil: el mismo binario sirve para cualquier devolución. Pero el defecto de los números negativos sigue ahí. Pruébalo introduciendo 9 en los días transcurridos y verás Multa: -1.5 EUR.
- Versión 3: cálculos correctos con el operador ternario
Aquí entra en juego el operador ternario para saturar el retraso a cero, y de paso añadimos la descomposición en semanas y el estado del préstamo. Este es el fragmento que cambia:
// El retraso nunca puede ser negativo: si se devuelve antes de tiempo, es 0.
int diasRetraso = diasTranscurridos > DIAS_PRESTAMO
? diasTranscurridos - DIAS_PRESTAMO
: 0;
// Descomposicion del retraso: / da las semanas completas, % los dias sobrantes.
int semanasRetraso = diasRetraso / 7;
int diasSueltos = diasRetraso % 7;
// Dias que quedan de plazo (0 si ya ha vencido).
int diasRestantes = diasTranscurridos < DIAS_PRESTAMO
? DIAS_PRESTAMO - diasTranscurridos
: 0;
double multaBase = diasRetraso * TARIFA_DIARIA;
// Etiquetas de texto elegidas con ternarios.
String estado = diasRetraso > 0 ? "CON RETRASO" : "EN PLAZO";
String gravedad = diasRetraso == 0 ? "-"
: diasRetraso <= UMBRAL_LEVE ? "LEVE"
: "GRAVE";Este bloque concentra buena parte de lo aprendido en la lección de operadores: comparación, resta, división entera, módulo, multiplicación con promoción automática de int a double, ternario simple y ternario anidado. Nada de esto requiere if.
- Versión 4: la salida formateada y el tope de multa
Faltan dos cosas: aplicar el tope de multa y presentar el recibo como un documento. Ambas se resuelven con lo visto en la lección 01-06.
// Tope: si la multa base supera el maximo, se cobra el maximo.
double multaFinal = multaBase > MULTA_MAXIMA ? MULTA_MAXIMA : multaBase;
String topeAplicado = multaBase > MULTA_MAXIMA ? "SI" : "NO";
// Recibo formateado: etiquetas alineadas a la izquierda en ancho 22,
// importes alineados a la derecha con exactamente dos decimales.
System.out.println("=".repeat(ANCHO));
System.out.printf("%-22s %s%n", "Empleado:", empleado);
System.out.printf("%-22s %8.2f EUR%n", "MULTA A PAGAR:", multaFinal);Con esto, 3.0 se convierte en 3,00 y las columnas quedan alineadas. Ya tenemos el programa completo.
- El código completo final, comentado
package com.nexussoftware.bibliotech;
import java.util.Scanner;
/**
* BiblioTechApp - Sistema de gestion de la biblioteca tecnica interna
* de Nexus Software.
*
* Version 0.1 del modulo 1: registro lineal de la devolucion de un
* prestamo, con calculo de retraso y multa, y emision de un recibo.
*
* @author Equipo BiblioTech - Nexus Software
* @version 0.1
*/
public class BiblioTechApp {
public static void main(String[] args) {
// ---------------------------------------------------------------
// 1. RECURSOS
// ---------------------------------------------------------------
// Un unico Scanner para toda la aplicacion, conectado al teclado.
// Cerrar un Scanner sobre System.in cierra System.in, asi que
// nunca se crea mas de uno.
Scanner entrada = new Scanner(System.in);
// ---------------------------------------------------------------
// 2. REGLAS DE NEGOCIO
// ---------------------------------------------------------------
// Todas las constantes van juntas y arriba: cambiar una tarifa
// es editar UNA linea, no buscar el numero por todo el programa.
// 'final' garantiza que nadie las modifique por accidente.
final String NOMBRE_EMPRESA = "Nexus Software";
final String VERSION = "0.1";
final int DIAS_PRESTAMO = 15; // duracion estandar del prestamo
final double TARIFA_DIARIA = 0.25; // euros por dia de retraso
final double MULTA_MAXIMA = 20.0; // tope absoluto de la multa
final int UMBRAL_LEVE = 7; // hasta 7 dias, el retraso es leve
final int DIAS_SEMANA = 7;
final int ANCHO = 52; // ancho de las lineas del recibo
// ---------------------------------------------------------------
// 3. CABECERA DE BIENVENIDA
// ---------------------------------------------------------------
// "=".repeat(ANCHO) construye la linea separadora sin teclear
// 52 signos igual a mano. repeat() existe desde Java 11.
System.out.println("=".repeat(ANCHO));
System.out.printf(" BiblioTech v%s - %s%n", VERSION, NOMBRE_EMPRESA);
System.out.println(" Registro de devolucion de prestamo");
System.out.println("=".repeat(ANCHO));
System.out.println();
// ---------------------------------------------------------------
// 4. RECOGIDA DE DATOS
// ---------------------------------------------------------------
// Se usa print (sin ln) para que el cursor se quede en la misma
// linea que la pregunta.
// TODA la lectura se hace con nextLine(): asi nunca queda un
// salto de linea pendiente en el flujo de entrada, que es lo que
// provoca el clasico problema de nextInt() seguido de nextLine().
System.out.print("Empleado : ");
String empleado = entrada.nextLine();
System.out.print("Titulo del libro : ");
String titulo = entrada.nextLine();
System.out.print("ISBN : ");
String isbn = entrada.nextLine();
System.out.print("Dias transcurridos : ");
// El texto leido se convierte a numero con parseInt.
// Si el usuario escribe algo que no es un entero, el programa
// termina con NumberFormatException (se tratara en el modulo 6).
int diasTranscurridos = Integer.parseInt(entrada.nextLine());
// ---------------------------------------------------------------
// 5. CALCULOS
// ---------------------------------------------------------------
// Dias de retraso saturados a cero. Sin el ternario, una devolucion
// anticipada daria un retraso negativo y, por tanto, una multa
// negativa: la biblioteca pagando al empleado.
int diasRetraso = diasTranscurridos > DIAS_PRESTAMO
? diasTranscurridos - DIAS_PRESTAMO
: 0;
// Dias que quedan de plazo. Solo tiene sentido si aun no ha vencido.
int diasRestantes = diasTranscurridos < DIAS_PRESTAMO
? DIAS_PRESTAMO - diasTranscurridos
: 0;
// Descomposicion del retraso: la division entera da las semanas
// completas y el modulo da los dias que sobran.
int semanasRetraso = diasRetraso / DIAS_SEMANA;
int diasSueltos = diasRetraso % DIAS_SEMANA;
// Multa base. diasRetraso es int y TARIFA_DIARIA es double:
// Java promociona el int a double antes de multiplicar.
double multaBase = diasRetraso * TARIFA_DIARIA;
// Tope de la multa. El importe cobrado nunca supera MULTA_MAXIMA.
double multaFinal = multaBase > MULTA_MAXIMA ? MULTA_MAXIMA : multaBase;
String topeAplicado = multaBase > MULTA_MAXIMA ? "SI" : "NO";
// Etiquetas de texto derivadas de los calculos.
String estado = diasRetraso > 0 ? "CON RETRASO" : "EN PLAZO";
// Ternario anidado, leido como una escalera de arriba abajo.
String gravedad = diasRetraso == 0 ? "-"
: diasRetraso <= UMBRAL_LEVE ? "LEVE"
: "GRAVE";
// Codigo de referencia del recibo: iniciales del empleado en
// mayusculas + los cuatro ultimos digitos del ISBN.
// charAt(0) toma el primer caracter; substring recorta desde la
// posicion indicada hasta el final.
String referencia = String.format("%c%s-%s",
empleado.charAt(0),
titulo.substring(0, 3).toUpperCase(),
isbn.substring(isbn.length() - 4));
// ---------------------------------------------------------------
// 6. RECIBO
// ---------------------------------------------------------------
System.out.println();
System.out.println("=".repeat(ANCHO));
System.out.println(" RECIBO DE DEVOLUCION");
System.out.println("=".repeat(ANCHO));
// %-22s alinea la etiqueta a la IZQUIERDA en ancho 22, creando
// una columna uniforme. %s pega el valor a continuacion.
System.out.printf("%-22s %s%n", "Referencia:", referencia);
System.out.printf("%-22s %s%n", "Empleado:", empleado);
System.out.printf("%-22s %s%n", "Libro:", titulo);
System.out.printf("%-22s %s%n", "ISBN:", isbn);
System.out.println("-".repeat(ANCHO));
// %5d alinea los enteros a la DERECHA en ancho 5, de forma que
// las unidades quedan en la misma columna y se comparan de un vistazo.
System.out.printf("%-22s %5d dias%n", "Prestamo estandar:", DIAS_PRESTAMO);
System.out.printf("%-22s %5d dias%n", "Transcurridos:", diasTranscurridos);
System.out.printf("%-22s %5d dias%n", "Restantes de plazo:", diasRestantes);
System.out.printf("%-22s %5d dias (%d sem. y %d dias)%n",
"Retraso:", diasRetraso, semanasRetraso, diasSueltos);
System.out.println("-".repeat(ANCHO));
// %8.2f: ancho total 8, exactamente 2 decimales. Es lo que
// convierte un 3.0 crudo en un 3,00 presentable.
System.out.printf("%-22s %8.2f EUR%n", "Tarifa diaria:", TARIFA_DIARIA);
System.out.printf("%-22s %8.2f EUR%n", "Multa calculada:", multaBase);
System.out.printf("%-22s %8.2f EUR%n", "Multa maxima:", MULTA_MAXIMA);
System.out.printf("%-22s %8s%n", "Tope aplicado:", topeAplicado);
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %8.2f EUR%n", "TOTAL A PAGAR:", multaFinal);
System.out.printf("%-22s %s (%s)%n", "Estado:", estado, gravedad);
System.out.println("=".repeat(ANCHO));
// Aviso por el canal de error: no forma parte del recibo y puede
// redirigirse por separado al ejecutar el programa.
System.err.println("[aviso] Version 0.1: el programa no valida las entradas.");
// ---------------------------------------------------------------
// 7. LIBERACION DE RECURSOS
// ---------------------------------------------------------------
entrada.close();
}
}
- Compilar y ejecutar
BiblioTechApp
BiblioTechAppEl fichero debe estar en la ruta que corresponde a su paquete:
bibliotech/
└── src/
└── main/
└── java/
└── com/
└── nexussoftware/
└── bibliotech/
└── BiblioTechApp.javaSituado en src/main/java:
# Compilar. Se indica la RUTA del fichero, con barras.
# -d envia los .class a una carpeta 'out' separada del codigo fuente.
javac -d ../../../out com/nexussoftware/bibliotech/BiblioTechApp.java
# Ejecutar. Se indica el NOMBRE DE LA CLASE cualificado, con puntos.
# -cp le dice al comando java donde buscar las clases compiladas.
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechAppSi prefieres trabajar sin paquete para probar rápido, elimina la primera línea (package ...;) y usa el modo de fichero único de Java 11+:
Y si redirigir la salida te resulta útil, recuerda que el recibo y el aviso van por canales distintos:
# El recibo se guarda en un fichero; el aviso sigue apareciendo en pantalla
java -cp ../../../out com.nexussoftware.bibliotech.BiblioTechApp > recibo.txtEn el IDE, basta con pulsar el botón de ejecutar sobre la clase; IntelliJ y Eclipse hacen por debajo exactamente estos dos comandos.
- Juegos de datos de prueba y salida esperada
Probar un programa significa ejecutarlo con datos elegidos a propósito para cubrir situaciones distintas. Estos son los dos casos que debes verificar:
| Dato | Juego A: préstamo con retraso | Juego B: préstamo en plazo |
|---|---|---|
| Empleado | Marta Ruiz |
Diego Alonso |
| Título | Java Efectivo |
Refactorizacion |
| ISBN | 978-0000000001 |
978-0000000003 |
| Días transcurridos | 27 |
9 |
| Días de retraso esperados | 12 | 0 |
| Días restantes esperados | 0 | 6 |
| Descomposición | 1 semana y 5 días | 0 semanas y 0 días |
| Multa calculada | 3,00 EUR | 0,00 EUR |
| Tope aplicado | NO | NO |
| Total a pagar | 3,00 EUR | 0,00 EUR |
| Estado | CON RETRASO (GRAVE) | EN PLAZO (-) |
| Referencia | MJAV-0001 | DREF-0003 |
Salida completa del juego A
==================================================== BiblioTech v0.1 - Nexus Software Registro de devolucion de prestamo ==================================================== Empleado : Marta Ruiz Titulo del libro : Java Efectivo ISBN : 978-0000000001 Dias transcurridos : 27 ==================================================== RECIBO DE DEVOLUCION ==================================================== Referencia: MJAV-0001 Empleado: Marta Ruiz Libro: Java Efectivo ISBN: 978-0000000001 ---------------------------------------------------- Prestamo estandar: 15 dias Transcurridos: 27 dias Restantes de plazo: 0 dias Retraso: 12 dias (1 sem. y 5 dias) ---------------------------------------------------- Tarifa diaria: 0,25 EUR Multa calculada: 3,00 EUR Multa maxima: 20,00 EUR Tope aplicado: NO ---------------------------------------------------- TOTAL A PAGAR: 3,00 EUR Estado: CON RETRASO (GRAVE) ====================================================
Salida completa del juego B
==================================================== BiblioTech v0.1 - Nexus Software Registro de devolucion de prestamo ==================================================== Empleado : Diego Alonso Titulo del libro : Refactorizacion ISBN : 978-0000000003 Dias transcurridos : 9 ==================================================== RECIBO DE DEVOLUCION ==================================================== Referencia: DREF-0003 Empleado: Diego Alonso Libro: Refactorizacion ISBN: 978-0000000003 ---------------------------------------------------- Prestamo estandar: 15 dias Transcurridos: 9 dias Restantes de plazo: 6 dias Retraso: 0 dias (0 sem. y 0 dias) ---------------------------------------------------- Tarifa diaria: 0,25 EUR Multa calculada: 0,00 EUR Multa maxima: 20,00 EUR Tope aplicado: NO ---------------------------------------------------- TOTAL A PAGAR: 0,00 EUR Estado: EN PLAZO (-) ====================================================
Observa el valor del juego B: el caso "en plazo" es el que demuestra que el ternario funciona. Sin él, el retraso sería -6 y la multa -1,50 EUR. Elegir datos que ejerciten los casos límite, y no solo el caso normal, es el núcleo de las pruebas de software (módulo 11).
Un tercer caso que merece la pena probar por tu cuenta: Nuria Vidal, Patrones de Diseno, ISBN 978-0000000002, 120 días. El retraso sería de 105 días y la multa calculada de 26,25 EUR, por encima del máximo: el programa debe mostrar Tope aplicado: SI y un total de 20,00 EUR.
- Las limitaciones del programa y cómo se resolverán
BiblioTechApp funciona, pero es un programa de juguete y conviene ser explícito sobre por qué. Reconocer las carencias de tu propio código es una de las habilidades más valiosas de un desarrollador.
| Limitación | Qué ocurre hoy | Cuándo se resuelve |
|---|---|---|
| No valida las entradas | Si escribes veintisiete en los días, el programa se estrella con NumberFormatException |
Módulo 2 (comprobar con hasNextInt) y módulo 6 (capturar la excepción y reaccionar) |
| No repite | Procesa una devolución y termina. Para la siguiente hay que volver a ejecutarlo | Módulo 2: bucles y menú interactivo (lección 02-06) |
| No toma decisiones complejas | Todo se resuelve con ternarios, que solo eligen valores, no ejecutan acciones distintas | Módulo 2: if, switch |
| Los datos están desperdigados | titulo, autor, isbn y disponible son variables sueltas sin relación entre sí |
Módulo 3: la clase Libro los agrupará en un tipo propio |
| Solo maneja un libro y un empleado | No hay forma de gestionar un catálogo de cientos de títulos | Módulo 5: ArrayList y HashMap |
| No guarda nada | Al cerrar el programa, el recibo desaparece para siempre | Módulo 7: escritura de ficheros, CSV |
| No avisa a nadie | Nadie recibe un recordatorio antes de que venza el plazo | Módulo 8: tareas en segundo plano |
| Solo funciona en tu máquina | Un compañero no puede consultar el catálogo desde su equipo | Módulos 9 y 12: red y aplicación web |
Usa double para dinero |
Los importes pueden acumular errores de redondeo | Módulo 10: BigDecimal |
| No hay pruebas | Verificas la salida a ojo, ejecutando a mano | Módulo 11: JUnit |
Fíjate en la lógica del curso: cada módulo posterior elimina una limitación concreta y visible del programa que acabas de escribir. No aprenderás bucles porque toque, sino porque hoy tu programa solo procesa una devolución. No aprenderás clases porque sí, sino porque hoy titulo y autor son dos variables sin nada que las una.
Errores Comunes y Consejos
- Olvidar el
entrada.nextLine()extra tras unnextInt(). En este programa no ocurre porque toda la lectura es connextLine(), pero si mezclas métodos volverá a aparecer. - Escribir
0,25en lugar de0.25al introducir decimales.Double.parseDoubleespera el punto, aunqueprintfmuestre coma. - Que
substring(isbn.length() - 4)falle. Si el ISBN tiene menos de 4 caracteres, lanzaStringIndexOutOfBoundsException. En esta versión asumimos entradas correctas. - Que
empleado.charAt(0)falle si el usuario pulsa Enter sin escribir nada: la cadena vacía no tiene carácter en la posición 0. - Usar
%dcon undoubleen elprintfdel total. ProvocaIllegalFormatConversionException. - Olvidar
%nen algúnprintf, lo que pega dos líneas del recibo. - Poner las constantes dispersas por el código. Agrúpalas al principio: es la diferencia entre cambiar la tarifa en diez segundos o buscar el
0.25por todo el fichero. - Consejo: cuando un cálculo tenga varios pasos (multa base → tope → total), crea una variable por paso con nombre descriptivo. Una sola expresión gigante es imposible de depurar.
- Consejo: ejecuta el programa con los tres juegos de datos (retraso normal, en plazo, tope superado) cada vez que lo modifiques. Es tu primera batería de pruebas manual, y en el módulo 11 la convertirás en automática.
- Consejo: guarda esta versión de
BiblioTechApp.java. En el módulo 2 partirás de ella para añadir el menú interactivo, y comparar ambas versiones te mostrará tu propio progreso.
Ejercicios
Ejercicio 1: Ampliar el recibo con la fianza y el aviso de vencimiento
Modifica BiblioTechApp para añadir al recibo:
- Una fianza del 10 % del precio del libro (pídelo por consola como un
double), que se devuelve íntegra si no hay retraso y se reduce en el importe de la multa si lo hay. La fianza a devolver nunca puede ser negativa. - Un aviso de vencimiento próximo: un texto que valga
"VENCE PRONTO"si quedan 3 días o menos de plazo (y aún no ha vencido),"EN CURSO"si quedan más, y"VENCIDO"si ya hay retraso.
Usa solo ternarios, sin if. Formatea todos los importes con dos decimales.
Ejercicio 2: Porcentajes y estadísticas del préstamo
Amplía el recibo con tres indicadores calculados, todos con dos decimales:
- El porcentaje de plazo consumido:
diasTranscurridos / DIAS_PRESTAMO * 100. Cuidado con la división entera. - El coste medio por día transcurrido: la multa final dividida entre los días transcurridos.
- El porcentaje de la multa máxima que representa la multa final.
Muestra los tres alineados en una sección "INDICADORES" del recibo.
Ejercicio 3: Recibo para dos préstamos simultáneos
Sin usar bucles ni arrays, amplía el programa para que registre la devolución de dos libros del mismo empleado y muestre un recibo conjunto con:
- Los datos de ambos libros en una tabla alineada con
printf. - La multa individual de cada uno.
- El total acumulado, sujeto a un tope conjunto de
MULTA_MAXIMA. - Cuál de los dos libros acumula más retraso (con el operador ternario).
Reflexiona al final: ¿qué pasaría si Nexus Software quisiera procesar diez libros? Anota tu respuesta; la retomaremos en el módulo 5.
Soluciones
Solución 1
Añade la lectura del precio y este bloque de cálculos y salida (el resto del programa no cambia):
// --- Lectura adicional ---
System.out.print("Precio del libro : ");
double precioLibro = Double.parseDouble(entrada.nextLine());
// --- Calculos de fianza y aviso ---
final double PORCENTAJE_FIANZA = 0.10; // 10 % del precio
final int UMBRAL_AVISO = 3; // dias para avisar del vencimiento
// La fianza es el 10 % del precio: double * double -> double.
double fianza = precioLibro * PORCENTAJE_FIANZA;
// Se descuenta la multa, pero nunca puede quedar en negativo:
// el ternario satura el resultado a cero.
double fianzaDevuelta = fianza - multaFinal > 0
? fianza - multaFinal
: 0.0;
// La retencion es la parte de la fianza que se queda la biblioteca.
double fianzaRetenida = fianza - fianzaDevuelta;
// Aviso de vencimiento: escalera de ternarios.
// Se evalua de arriba abajo y se para en la primera condicion cierta.
String aviso = diasRetraso > 0 ? "VENCIDO"
: diasRestantes <= UMBRAL_AVISO ? "VENCE PRONTO"
: "EN CURSO";
// --- Salida adicional ---
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %8.2f EUR%n", "Precio del libro:", precioLibro);
System.out.printf("%-22s %8.2f EUR%n", "Fianza depositada:", fianza);
System.out.printf("%-22s %8.2f EUR%n", "Fianza retenida:", fianzaRetenida);
System.out.printf("%-22s %8.2f EUR%n", "FIANZA A DEVOLVER:", fianzaDevuelta);
System.out.printf("%-22s %s%n", "Aviso:", aviso);Comprobación con los tres casos:
| Caso | Precio | Fianza (10 %) | Multa | Retenida | A devolver | Aviso |
|---|---|---|---|---|---|---|
| Marta, 27 días | 45,90 | 4,59 | 3,00 | 3,00 | 1,59 | VENCIDO |
| Diego, 9 días | 38,75 | 3,88 | 0,00 | 0,00 | 3,88 | EN CURSO |
| Diego, 13 días | 38,75 | 3,88 | 0,00 | 0,00 | 3,88 | VENCE PRONTO |
El punto clave del ejercicio es el orden de la escalera de ternarios: la comprobación de diasRetraso > 0 debe ir la primera. Si se pusiera después, un préstamo vencido con diasRestantes igual a 0 entraría por la rama de "VENCE PRONTO", que es falsa.
Solución 2
// --- Indicadores ---
// Cuidado: diasTranscurridos y DIAS_PRESTAMO son int, asi que
// la division seria ENTERA. Hay que forzar un double antes.
double porcentajePlazo = (double) diasTranscurridos / DIAS_PRESTAMO * 100;
// Coste medio por dia transcurrido. Aqui multaFinal ya es double,
// asi que la promocion es automatica y no hace falta cast.
double costeMedioDiario = multaFinal / diasTranscurridos;
// Que porcentaje del tope representa lo que se va a cobrar.
double porcentajeMaximo = multaFinal / MULTA_MAXIMA * 100;
System.out.println("-".repeat(ANCHO));
System.out.println(" INDICADORES");
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %8.2f %%%n", "Plazo consumido:", porcentajePlazo);
System.out.printf("%-22s %8.4f EUR/dia%n", "Coste medio diario:", costeMedioDiario);
System.out.printf("%-22s %8.2f %%%n", "Sobre multa maxima:", porcentajeMaximo);Salida para el juego A (Marta Ruiz, 27 días, multa 3,00):
---------------------------------------------------- INDICADORES ---------------------------------------------------- Plazo consumido: 180,00 % Coste medio diario: 0,1111 EUR/dia Sobre multa maxima: 15,00 %
Tres detalles importantes:
- El cast
(double)es obligatorio en el primer cálculo. Sin él,27 / 15daría1(división entera) y el porcentaje sería100,00 %en lugar de180,00 %. Es la trampa de la lección 01-05 en un caso real. %%imprime un símbolo de porcentaje literal. Un%suelto haría queprintfintentara interpretarlo como el inicio de un especificador y lanzaraUnknownFormatConversionException.%.4fen el coste diario porque con dos decimales el valor0,1111se redondearía a0,11y perdería significado. La precisión debe elegirse según la magnitud del dato.
Solución 3
// ================= SEGUNDO LIBRO =================
System.out.println();
System.out.println("--- Segundo libro ---");
System.out.print("Titulo del libro 2 : ");
String titulo2 = entrada.nextLine();
System.out.print("ISBN 2 : ");
String isbn2 = entrada.nextLine();
System.out.print("Dias transcurridos 2: ");
int diasTranscurridos2 = Integer.parseInt(entrada.nextLine());
// Los mismos calculos que para el primero, duplicados.
// Esta duplicacion es precisamente lo que resolveran los METODOS
// (modulo 3) y los BUCLES sobre colecciones (modulo 5).
int diasRetraso2 = diasTranscurridos2 > DIAS_PRESTAMO
? diasTranscurridos2 - DIAS_PRESTAMO
: 0;
double multaBase2 = diasRetraso2 * TARIFA_DIARIA;
// Total conjunto, sujeto a un unico tope para toda la devolucion.
double totalBruto = multaBase + multaBase2;
double totalFinal = totalBruto > MULTA_MAXIMA ? MULTA_MAXIMA : totalBruto;
// Cual acumula mas retraso.
String libroMasRetrasado = diasRetraso >= diasRetraso2 ? titulo : titulo2;
int mayorRetraso = diasRetraso >= diasRetraso2 ? diasRetraso : diasRetraso2;
// ================= RECIBO CONJUNTO =================
System.out.println();
System.out.println("=".repeat(ANCHO));
System.out.println(" RECIBO CONJUNTO DE DEVOLUCION");
System.out.println("=".repeat(ANCHO));
System.out.printf("%-22s %s%n", "Empleado:", empleado);
System.out.println("-".repeat(ANCHO));
// Cabecera de la tabla y dos filas con el mismo formato.
System.out.printf("%-24s %-16s %5s %9s%n", "LIBRO", "ISBN", "RETR.", "MULTA");
System.out.printf("%-24s %-16s %5d %9.2f%n", titulo, isbn, diasRetraso, multaBase);
System.out.printf("%-24s %-16s %5d %9.2f%n", titulo2, isbn2, diasRetraso2, multaBase2);
System.out.println("-".repeat(ANCHO));
System.out.printf("%-22s %8.2f EUR%n", "Suma de multas:", totalBruto);
System.out.printf("%-22s %8.2f EUR%n", "TOTAL A PAGAR:", totalFinal);
System.out.printf("%-22s %s (%d dias)%n", "Mayor retraso:", libroMasRetrasado, mayorRetraso);
System.out.println("=".repeat(ANCHO));Ejemplo de salida con Marta Ruiz, "Java Efectivo" (27 días) y "Patrones de Diseno" (40 días):
==================================================== RECIBO CONJUNTO DE DEVOLUCION ==================================================== Empleado: Marta Ruiz ---------------------------------------------------- LIBRO ISBN RETR. MULTA Java Efectivo 978-0000000001 12 3,00 Patrones de Diseno 978-0000000002 25 6,25 ---------------------------------------------------- Suma de multas: 9,25 EUR TOTAL A PAGAR: 9,25 EUR Mayor retraso: Patrones de Diseno (25 dias) ====================================================
La reflexión final del ejercicio es la más importante de todo el módulo. Para diez libros necesitarías titulo1 … titulo10, isbn1 … isbn10, diasRetraso1 … diasRetraso10: treinta variables, treinta bloques de lectura idénticos y treinta cálculos copiados. El código sería tres veces más largo, imposible de mantener, y bastaría con teclear diasRetraso3 donde querías diasRetraso4 para producir un recibo erróneo que nadie detectaría.
Ese dolor tiene tres soluciones y las verás en este orden: los bucles (módulo 2) eliminan la repetición del proceso; las clases (módulo 3) agrupan titulo, isbn y diasRetraso en un único objeto Libro; y las colecciones (módulo 5) guardan tantos libros como haga falta en una sola variable. No es casualidad que el curso siga precisamente ese camino.
Conclusión
Has escrito tu primer programa Java completo y funcional. BiblioTechApp da la bienvenida al usuario, recoge por consola los datos de una devolución, calcula los días de retraso saturados a cero con el operador ternario, los descompone en semanas y días con la división entera y el módulo, obtiene la multa promocionando automáticamente int a double, le aplica un tope, deriva etiquetas de estado y gravedad con ternarios anidados, compone una referencia con métodos de String y emite un recibo perfectamente alineado con printf. Todo ello con un único main, constantes final bien agrupadas y un único Scanner correctamente cerrado.
Con esta lección cierras el módulo 1. Sabes qué es Java y cómo se ejecuta, tienes un entorno de desarrollo funcionando, entiendes la anatomía de un fichero fuente, manejas variables, tipos, literales y conversiones, dominas los operadores y sus trampas, y sabes comunicarte con el usuario por consola en ambas direcciones. Y, sobre todo, sabes exactamente qué le falta a tu programa: no repite, no decide, no valida y no recuerda nada.
En el módulo 2, Flujo de Control, empezarás a resolverlo: con if y switch tu programa tomará decisiones de verdad, con while y for procesará tantas devoluciones como haga falta, con break y continue afinará ese control, aprenderás a depurar paso a paso para ver qué ocurre dentro de tu código, y todo culminará en el menú interactivo de BiblioTech, la versión que ya no termina tras un solo préstamo.
Curso de Programación en Java
Módulo 1: Introducción a Java
- Introducción a Java
- Configuración del Entorno de Desarrollo
- Sintaxis y Estructura Básica
- Variables y Tipos de Datos
- Operadores
- Entrada y Salida por Consola
- Tu Primer Programa Completo: BiblioTech
Módulo 2: Flujo de Control
- Sentencias Condicionales
- Bucles
- Sentencias Switch
- Break y Continue
- Depuración y Trazas de Ejecución
- Proyecto: Menú Interactivo de BiblioTech
Módulo 3: Programación Orientada a Objetos
- Introducción a la POO
- Clases y Objetos
- Métodos
- Constructores
- Herencia
- Polimorfismo
- Encapsulamiento
- Abstracción
- La Clase Object: equals, hashCode y toString
Módulo 4: Programación Orientada a Objetos Avanzada
- Interfaces
- Clases Abstractas
- Clases Internas
- Clases Anónimas
- Expresiones Lambda
- Interfaces Funcionales y Referencias a Métodos
- Enumeraciones y Registros
Módulo 5: Estructuras de Datos y Colecciones
- Arreglos
- El Framework de Colecciones
- ArrayList
- LinkedList
- HashMap
- HashSet
- Cola y Deque
- Pila
- Ordenación y Búsqueda en Colecciones
Módulo 6: Manejo de Excepciones
- Introducción a las Excepciones
- Bloque Try-Catch
- Throw y Throws
- Excepciones Personalizadas
- Bloque Finally
- Try-with-resources y AutoCloseable
- Estrategias de Manejo de Errores y Logging
Módulo 7: Entrada/Salida de Archivos
- Lectura de Archivos
- Escritura de Archivos
- Flujos de Archivos
- BufferedReader y BufferedWriter
- Serialización
- La API NIO.2: Path y Files
- Formatos de Intercambio: CSV y Properties
Módulo 8: Multihilo y Concurrencia
- Introducción al Multihilo
- Creación de Hilos
- Ciclo de Vida de un Hilo
- Sincronización
- Utilidades de Concurrencia
- Colecciones Concurrentes y Variables Atómicas
- Tareas Asíncronas con CompletableFuture
Módulo 9: Redes
- Introducción a las Redes
- Sockets
- ServerSocket
- DatagramSocket y DatagramPacket
- URL y HttpURLConnection
- El Cliente HTTP Moderno
Módulo 10: Temas Avanzados
- Genéricos
- Anotaciones
- Reflexión
- Características de Java 8: Streams y Optional
- Fechas y Horas con java.time
- Java 9 y Más Allá
- Memoria, Recolección de Basura y Rendimiento
Módulo 11: Frameworks y Librerías de Java
- Introducción a los Frameworks de Java
- Spring Framework
- Hibernate
- JUnit
- Maven
- Pruebas Avanzadas con Mockito
- Librerías Esenciales del Ecosistema
