Apuntes DAM
Volver al inicio

Parking con tarifas por tipo de vehículo

Ejercicio de JavaDifícilUnos 75 minutos

Gestiona las entradas y salidas de un parking con plazas para coches, motos y furgonetas: matrículas validadas, plazas por tipo, tarifa por cuarto de hora con 10 minutos gratis y máximo diario, y la recaudación del día. Herencia, clases abstractas, polimorfismo y mapas.

  • Clases abstractas
  • Herencia y @Override
  • Polimorfismo
  • HashMap
  • switch con flechas
  • Expresiones regulares

Enunciado

El aparcamiento de un centro comercial tiene 4 plazas para coches, 2 para motos y 1 para furgonetas (es una versión de prueba: el real tiene cientos). La barrera de entrada y la de salida envían al programa una línea por cada vehículo que pasa.

Cada tipo de vehículo paga distinto, pero todos con la misma regla: los 10 primeros minutos son gratis (para quien entra y se va) y, a partir de ahí, se cobra cada cuarto de hora empezado, con un máximo por día. Esa regla común es justo lo que debe ir en una clase padre, y lo que cambia (el precio y el máximo), en cada clase hija.

Las cámaras leen las matrículas en el formato actual español: cuatro cifras y tres consonantes, sin vocales, ni Ñ ni Q, para evitar palabras y confusiones.

Qué tiene que hacer el programa

  1. Cada línea es una orden: ENTRA HH:MM tipo matrícula, SALE HH:MM matrícula u OCUPACION. La orden, el tipo y la matrícula pueden venir en mayúsculas o minúsculas (la matrícula se guarda y se muestra en mayúsculas). Una línea con un número de palabras distinto del de su orden escribe Orden incompleta; una orden desconocida, Orden desconocida. Las líneas vacías se ignoran.
  2. ENTRA comprueba, en este orden: la hora (Hora no válida: X, de 00:00 a 23:59), el tipo (Tipo no válido: X; son coche, moto y furgoneta), la matrícula (Matrícula no válida: X: 4 cifras y 3 letras de BCDFGHJKLMNPRSTVWXYZ), que no esté ya dentro (X ya está dentro) y que queden plazas de su tipo (No quedan plazas de tipo). Si todo va bien: Entra X (tipo) a las HH:MM.
  3. SALE comprueba la hora, que el vehículo esté dentro (X no está en el parking) y que la salida no sea anterior a la entrada (La hora de salida es anterior a la entrada). Si todo va bien, el vehículo deja libre su plaza y se escribe Sale X (tipo) tras D: I, con la duración como 2 h 35 min, 3 h o 8 min y el importe en euros (6,60 €).
  4. Tarifas: hasta 10 minutos, 0 €. Después, por cada 15 minutos empezados: coche 0,60 €, moto 0,30 € y furgoneta 0,90 €, con un máximo de 18 €, 9 € y 25 € respectivamente. Todas las estancias son del mismo día.
  5. OCUPACION escribe Ocupación: coche 2/4 · moto 0/2 · furgoneta 1/1.
  6. Al terminar la entrada: Recaudación: X (N salidas) («salida» si es 1) y Siguen dentro: … con las matrículas que no han salido en orden alfabético, separadas por coma y espacio, o ninguno.

Entrada

Una orden por línea: ENTRA HH:MM tipo matrícula, SALE HH:MM matrícula u OCUPACION.

Datos de referencia

Plazas y tarifas
TipoPlazasCada 15 min empezadosMáximo diario
coche40,60 €18,00 €
moto20,30 €9,00 €
furgoneta10,90 €25,00 €

Ejemplos de ejecución

Tu programa debe escribir exactamente esta salida para estas entradas. Las pruebas del editor incluyen estos ejemplos y otros casos ocultos.

Un día de parking

Entrada

ENTRA 08:15 coche 1234BCD
ENTRA 08:20 moto 5678FGH
ENTRA 08:25 furgoneta 9012JKL
ENTRA 08:30 furgoneta 3456MNP
OCUPACION
SALE 08:28 5678FGH
SALE 10:50 1234BCD
ENTRA 11:00 coche 1234bcd
SALE 19:45 9012JKL
SALE 12:00 0000BBB

Salida por consola

Entra 1234BCD (coche) a las 08:15
Entra 5678FGH (moto) a las 08:20
Entra 9012JKL (furgoneta) a las 08:25
No quedan plazas de furgoneta
Ocupación: coche 1/4 · moto 1/2 · furgoneta 1/1
Sale 5678FGH (moto) tras 8 min: 0,00 €
Sale 1234BCD (coche) tras 2 h 35 min: 6,60 €
Entra 1234BCD (coche) a las 11:00
Sale 9012JKL (furgoneta) tras 11 h 20 min: 25,00 €
0000BBB no está en el parking
Recaudación: 31,60 € (3 salidas)
Siguen dentro: 1234BCD

Datos no válidos

Entrada

ENTRA 25:00 coche 1234BCD
ENTRA 09:00 bici 1234BCD
ENTRA 09:00 coche 1234ABC
ENTRA 09:00 coche 1234BC
ENTRA 09:05 moto 1111BBB
ENTRA 09:06 moto 1111BBB
SALE 09:00 1111BBB
ENTRA
ESTADO

Salida por consola

Hora no válida: 25:00
Tipo no válido: bici
Matrícula no válida: 1234ABC
Matrícula no válida: 1234BC
Entra 1111BBB (moto) a las 09:05
1111BBB ya está dentro
La hora de salida es anterior a la entrada
Orden incompleta
Orden desconocida
Recaudación: 0,00 € (0 salidas)
Siguen dentro: 1111BBB

Guía paso a paso

Intenta resolverlo por tu cuenta y abre un paso solo cuando te atasques: cada uno te acerca a la solución sin dártela entera.

1. La clase abstracta Vehiculo

Pon en Vehiculo lo común: la matrícula, la hora de entrada y el cálculo del importe. Lo que cambia de un tipo a otro son dos números, así que decláralos como métodos abstractos que cada subclase tendrá que implementar.

java
abstract long precioFraccion();
abstract long maximoDiario();

long importe(int minutos) {
    if (minutos <= 10) return 0;
    long fracciones = (minutos + 14) / 15;   // cuartos de hora empezados
    return Math.min(fracciones * precioFraccion(), maximoDiario());
}
2. Las subclases

Coche, Moto y Furgoneta extienden Vehiculo, llaman a super(matricula, entrada) en su constructor y solo devuelven su tipo, su precio y su máximo. importe no se repite: lo heredan.

3. Los vehículos aparcados

Un HashMap<String, Vehiculo> con la matrícula como clave responde al instante si un vehículo está dentro y te lo devuelve al salir. Las plazas ocupadas de un tipo se cuentan recorriendo dentro.values().

4. Las horas como minutos

Pasa cada hora a minutos desde las 00:00: así la duración es una resta. Una expresión regular comprueba el formato y el rango de una vez.

java
if (!hora.matches("([01]\\d|2[0-3]):[0-5]\\d")) return -1;
return Integer.parseInt(hora.substring(0, 2)) * 60 + Integer.parseInt(hora.substring(3));
5. El polimorfismo en la salida

Al salir, v.importe(estancia) se llama igual para cualquier vehículo, pero cada objeto usa el precio de su clase. Tu código no necesita ningún if (tipo.equals("moto")) para cobrar.

Resuélvelo aquí

El editor trae el esqueleto del programa. Pulsa «Ejecutar» para comprobarlo con los ejemplos y con 2 casos ocultos que buscan los errores típicos.

☕JavaParking con tarifas por tipo de vehículoDifícil

Ejemplo

Entrada (lo que se escribe por teclado)
ENTRA 08:15 coche 1234BCD
ENTRA 08:20 moto 5678FGH
ENTRA 08:25 furgoneta 9012JKL
ENTRA 08:30 furgoneta 3456MNP
OCUPACION
SALE 08:28 5678FGH
SALE 10:50 1234BCD
ENTRA 11:00 coche 1234bcd
SALE 19:45 9012JKL
SALE 12:00 0000BBB
Salida esperada
Entra 1234BCD (coche) a las 08:15
Entra 5678FGH (moto) a las 08:20
Entra 9012JKL (furgoneta) a las 08:25
No quedan plazas de furgoneta
Ocupación: coche 1/4 · moto 1/2 · furgoneta 1/1
Sale 5678FGH (moto) tras 8 min: 0,00 €
Sale 1234BCD (coche) tras 2 h 35 min: 6,60 €
Entra 1234BCD (coche) a las 11:00
Sale 9012JKL (furgoneta) tras 11 h 20 min: 25,00 €
0000BBB no está en el parking
Recaudación: 31,60 € (3 salidas)
Siguen dentro: 1234BCD
⏳
Test oculto #3
⏳
Test oculto #4
0/4 tests pasados · pulsa un test para ver su entrada y su salida esperada

Solución explicada

Ver la solución completa
java
1import java.util.ArrayList;
2import java.util.HashMap;
3import java.util.List;
4import java.util.Locale;
5import java.util.Map;
6import java.util.Scanner;
7
8abstract class Vehiculo {
9    private final String matricula;
10    private final int entrada;   // minutos desde las 00:00
11
12    Vehiculo(String matricula, int entrada) {
13        this.matricula = matricula;
14        this.entrada = entrada;
15    }
16
17    String getMatricula() { return matricula; }
18    int getEntrada() { return entrada; }
19
20    abstract String tipo();
21    /** Céntimos por cada 15 minutos empezados. */
22    abstract long precioFraccion();
23    /** Lo máximo que se cobra en un día, en céntimos. */
24    abstract long maximoDiario();
25
26    /** Importe en céntimos de una estancia: 10 minutos gratis y, después, cada cuarto de hora empezado, con un máximo. */
27    long importe(int minutos) {
28        if (minutos <= 10) return 0;
29        long fracciones = (minutos + 14) / 15;
30        return Math.min(fracciones * precioFraccion(), maximoDiario());
31    }
32}
33
34class Coche extends Vehiculo {
35    Coche(String matricula, int entrada) { super(matricula, entrada); }
36    String tipo() { return "coche"; }
37    long precioFraccion() { return 60; }
38    long maximoDiario() { return 1800; }
39}
40
41class Moto extends Vehiculo {
42    Moto(String matricula, int entrada) { super(matricula, entrada); }
43    String tipo() { return "moto"; }
44    long precioFraccion() { return 30; }
45    long maximoDiario() { return 900; }
46}
47
48class Furgoneta extends Vehiculo {
49    Furgoneta(String matricula, int entrada) { super(matricula, entrada); }
50    String tipo() { return "furgoneta"; }
51    long precioFraccion() { return 90; }
52    long maximoDiario() { return 2500; }
53}
54
55public class Main {
56    static final List<String> TIPOS = List.of("coche", "moto", "furgoneta");
57    static final Map<String, Integer> PLAZAS = Map.of("coche", 4, "moto", 2, "furgoneta", 1);
58
59    static final Map<String, Vehiculo> dentro = new HashMap<>();
60    static long recaudado = 0;
61    static int salidas = 0;
62
63    /** «08:15» → 495 minutos; -1 si no es una hora válida. */
64    static int minutos(String hora) {
65        if (!hora.matches("([01]\\d|2[0-3]):[0-5]\\d")) return -1;
66        return Integer.parseInt(hora.substring(0, 2)) * 60 + Integer.parseInt(hora.substring(3));
67    }
68
69    static String euros(long centimos) {
70        return String.format(Locale.ROOT, "%d,%02d €", centimos / 100, centimos % 100);
71    }
72
73    static String duracion(int minutos) {
74        int h = minutos / 60, m = minutos % 60;
75        if (h == 0) return m + " min";
76        return m == 0 ? h + " h" : h + " h " + m + " min";
77    }
78
79    static int ocupadas(String tipo) {
80        int n = 0;
81        for (Vehiculo v : dentro.values()) if (v.tipo().equals(tipo)) n++;
82        return n;
83    }
84
85    static void entra(String hora, String tipo, String matricula) {
86        int min = minutos(hora);
87        if (min < 0) { System.out.println("Hora no válida: " + hora); return; }
88        if (!TIPOS.contains(tipo)) { System.out.println("Tipo no válido: " + tipo); return; }
89        // Matrículas españolas: 4 cifras y 3 consonantes (sin vocales, Ñ ni Q)
90        if (!matricula.matches("\\d{4}[BCDFGHJKLMNPRSTVWXYZ]{3}")) { System.out.println("Matrícula no válida: " + matricula); return; }
91        if (dentro.containsKey(matricula)) { System.out.println(matricula + " ya está dentro"); return; }
92        if (ocupadas(tipo) == PLAZAS.get(tipo)) { System.out.println("No quedan plazas de " + tipo); return; }
93
94        Vehiculo v = switch (tipo) {
95            case "coche" -> new Coche(matricula, min);
96            case "moto" -> new Moto(matricula, min);
97            default -> new Furgoneta(matricula, min);
98        };
99        dentro.put(matricula, v);
100        System.out.println("Entra " + matricula + " (" + tipo + ") a las " + hora);
101    }
102
103    static void sale(String hora, String matricula) {
104        int min = minutos(hora);
105        if (min < 0) { System.out.println("Hora no válida: " + hora); return; }
106        Vehiculo v = dentro.get(matricula);
107        if (v == null) { System.out.println(matricula + " no está en el parking"); return; }
108        if (min < v.getEntrada()) { System.out.println("La hora de salida es anterior a la entrada"); return; }
109
110        int estancia = min - v.getEntrada();
111        long importe = v.importe(estancia);   // cada tipo de vehículo aplica su tarifa
112        dentro.remove(matricula);
113        recaudado += importe;
114        salidas++;
115        System.out.println("Sale " + matricula + " (" + v.tipo() + ") tras " + duracion(estancia) + ": " + euros(importe));
116    }
117
118    public static void main(String[] args) {
119        Scanner sc = new Scanner(System.in);
120        while (sc.hasNextLine()) {
121            String linea = sc.nextLine().trim();
122            if (linea.isEmpty()) continue;
123            String[] p = linea.split("\\s+");
124            switch (p[0].toUpperCase()) {
125                case "ENTRA" -> {
126                    if (p.length != 4) System.out.println("Orden incompleta");
127                    else entra(p[1], p[2].toLowerCase(), p[3].toUpperCase());
128                }
129                case "SALE" -> {
130                    if (p.length != 3) System.out.println("Orden incompleta");
131                    else sale(p[1], p[2].toUpperCase());
132                }
133                case "OCUPACION" -> {
134                    List<String> partes = new ArrayList<>();
135                    for (String tipo : TIPOS) partes.add(tipo + " " + ocupadas(tipo) + "/" + PLAZAS.get(tipo));
136                    System.out.println("Ocupación: " + String.join(" · ", partes));
137                }
138                default -> System.out.println("Orden desconocida");
139            }
140        }
141
142        System.out.println("Recaudación: " + euros(recaudado) + " (" + salidas + (salidas == 1 ? " salida)" : " salidas)"));
143        List<String> quedan = new ArrayList<>(dentro.keySet());
144        quedan.sort(null);
145        System.out.println("Siguen dentro: " + (quedan.isEmpty() ? "ninguno" : String.join(", ", quedan)));
146    }
147}

La clase abstracta recoge la regla de cobro, que es la misma para todos, y deja abiertos solo los dos datos que cambian. Cada subclase es pequeña y declarativa: añadir un tipo nuevo (por ejemplo, autocaravana) sería escribir una clase de cinco líneas y añadirla al switch de creación.

El único lugar donde aparece el tipo como texto es al crear el objeto. A partir de ahí, el programa trabaja con Vehiculo y el polimorfismo elige el precio correcto: es la idea central de la herencia.

La fórmula (minutos + 14) / 15 es el redondeo hacia arriba de una división entera: 16 minutos dan 2 cuartos de hora, 15 dan 1. Combinada con Math.min aplica el máximo diario sin un if más.

Las comprobaciones se hacen en métodos con return temprano: cada error sale en cuanto se detecta, y el camino normal queda al final sin anidar condiciones.

Para ir más allá

  • Admite estancias de varios días con fecha y hora, aplicando el máximo a cada día.
  • Añade abonados (una lista de matrículas que no pagan) con una clase o un atributo, y cuenta sus entradas aparte.
  • Guarda el histórico de salidas y añade INFORME con la recaudación por tipo de vehículo.

Dónde se explica