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

  1. La especificación del programa
  2. Versión 1: el esqueleto con datos fijos
  3. Versión 2: pedir los datos al usuario
  4. Versión 3: cálculos correctos con el operador ternario
  5. Versión 4: la salida formateada y el tope de multa
  6. El código completo final, comentado
  7. Compilar y ejecutar BiblioTechApp
  8. Juegos de datos de prueba y salida esperada
  9. Las limitaciones del programa y cómo se resolverán
  10. Errores Comunes y Consejos
  11. Ejercicios

  1. 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:

  1. Mostrar una cabecera de bienvenida con el nombre de la empresa y de la aplicación.
  2. 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ó
  3. 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.
  4. 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"]

  1. 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:

  1. Los datos están fijos. Para procesar otra devolución hay que editar el código y recompilar.
  2. Los cálculos son ingenuos. Si el libro se devuelve a los 9 días, diasRetraso valdría -6 y la multa -1.5 EUR: la biblioteca pagando al empleado.
  3. La salida es pobre. 3.0 EUR no es un importe presentable, y las etiquetas no están alineadas.

  1. 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.

  1. 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.

  1. 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.

  1. 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();
    }
}

  1. Compilar y ejecutar BiblioTechApp

El fichero debe estar en la ruta que corresponde a su paquete:

bibliotech/
└── src/
    └── main/
        └── java/
            └── com/
                └── nexussoftware/
                    └── bibliotech/
                        └── BiblioTechApp.java

Situado 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.BiblioTechApp

Si prefieres trabajar sin paquete para probar rápido, elimina la primera línea (package ...;) y usa el modo de fichero único de Java 11+:

java BiblioTechApp.java

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.txt

En el IDE, basta con pulsar el botón de ejecutar sobre la clase; IntelliJ y Eclipse hacen por debajo exactamente estos dos comandos.

  1. 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.

  1. 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 un nextInt(). En este programa no ocurre porque toda la lectura es con nextLine(), pero si mezclas métodos volverá a aparecer.
  • Escribir 0,25 en lugar de 0.25 al introducir decimales. Double.parseDouble espera el punto, aunque printf muestre coma.
  • Que substring(isbn.length() - 4) falle. Si el ISBN tiene menos de 4 caracteres, lanza StringIndexOutOfBoundsException. 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 %d con un double en el printf del total. Provoca IllegalFormatConversionException.
  • Olvidar %n en algún printf, 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.25 por 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:

  1. 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.
  2. 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:

  1. El porcentaje de plazo consumido: diasTranscurridos / DIAS_PRESTAMO * 100. Cuidado con la división entera.
  2. El coste medio por día transcurrido: la multa final dividida entre los días transcurridos.
  3. 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 / 15 daría 1 (división entera) y el porcentaje sería 100,00 % en lugar de 180,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 que printf intentara interpretarlo como el inicio de un especificador y lanzara UnknownFormatConversionException.
  • %.4f en el coste diario porque con dos decimales el valor 0,1111 se redondearía a 0,11 y 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 titulo1titulo10, isbn1isbn10, diasRetraso1diasRetraso10: 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

Módulo 2: Flujo de Control

Módulo 3: Programación Orientada a Objetos

Módulo 4: Programación Orientada a Objetos Avanzada

Módulo 5: Estructuras de Datos y Colecciones

Módulo 6: Manejo de Excepciones

Módulo 7: Entrada/Salida de Archivos

Módulo 8: Multihilo y Concurrencia

Módulo 9: Redes

Módulo 10: Temas Avanzados

Módulo 11: Frameworks y Librerías de Java

Módulo 12: Construcción de Aplicaciones del Mundo Real

© Copyright 2026. Todos los derechos reservados