Apuntes DAM
Volver al inicio

Ejercicios resueltos de Entornos de Desarrollo

Los 19 ejercicios de Entornos de Desarrollo de la web, tema a tema: cada uno con su enunciado, los datos que necesitas (código de partida, ejemplos de entrada y salida o la base de datos) y, al final, la solución explicada.

Descargar el PDF

Desarrollo de Software

1. Gráfico de trabajo pendiente (burndown) de un sprint

Difícil · Desarrollo de Software · apuntesdam.com/subject/entornos/topic/desarrollo-software

En Scrum, el burndown muestra cada día cuántos puntos de historia quedan por terminar. La entrada es la duración del sprint en días y, después, cada historia con su identificador, sus puntos y el día en que se terminó (0 si no se terminó). Muestra la cabecera «Sprint de D días, T puntos comprometidos» y, para cada día de 0 a D, una línea con el formato «Día %2d | barra | %2d (ideal %.1f)», donde la barra son tantos # como puntos quedan, rellenada con espacios hasta T caracteres, y el ideal es la recta que baja de T a 0 (T − T·d/D). Si quedan más puntos que el ideal redondeado hacia arriba, añade « (retraso)». Una historia solo descuenta sus puntos al terminarse (no hay puntos a medias). Al final, la velocidad (puntos terminados) y las historias que vuelven al product backlog (o «ninguna»).

Código de partida (java)
import java.util.*; public class Main {    record Historia(String id, int puntos, int diaTerminada) { }   // diaTerminada = 0 si no se terminó     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int dias = sc.nextInt();        List<Historia> hs = new ArrayList<>();        while (sc.hasNext()) hs.add(new Historia(sc.next(), sc.nextInt(), sc.nextInt()));        // TODO: mostrar el burndown día a día, la velocidad y las historias pendientes    }}

Ejemplo: Sprint que acaba bien

Entrada

5
H1 3 2
H2 5 4
H3 2 5

Salida esperada

Sprint de 5 días, 10 puntos comprometidos
Día  0 | ########## | 10 (ideal 10.0)
Día  1 | ########## | 10 (ideal 8.0)  (retraso)
Día  2 | #######    |  7 (ideal 6.0)  (retraso)
Día  3 | #######    |  7 (ideal 4.0)  (retraso)
Día  4 | ##         |  2 (ideal 2.0)
Día  5 |            |  0 (ideal 0.0)
Velocidad: 10 puntos
Vuelven al product backlog: ninguna

Ejemplo: Sprint con retraso

Entrada

4
H1 5 3
H2 3 0
H3 2 4
H4 1 1

Salida esperada

Sprint de 4 días, 11 puntos comprometidos
Día  0 | ########### | 11 (ideal 11.0)
Día  1 | ##########  | 10 (ideal 8.3)  (retraso)
Día  2 | ##########  | 10 (ideal 5.5)  (retraso)
Día  3 | #####       |  5 (ideal 2.8)  (retraso)
Día  4 | ###         |  3 (ideal 0.0)  (retraso)
Velocidad: 8 puntos
Vuelven al product backlog: H2

2. Planificación con dependencias y camino crítico

Muy difícil · Desarrollo de Software · apuntesdam.com/subject/entornos/topic/desarrollo-software

Para planificar un proyecto se descompone en tareas con su duración y las tareas que deben terminar antes (como en un diagrama de Gantt o PERT). Cada línea de la entrada es «ID duración» y, opcionalmente, la lista de tareas previas separadas por comas. Si una tarea depende de otra que no existe, muestra «Error: X depende de Y, que no existe». Calcula un orden topológico con el algoritmo de Kahn: empieza por las tareas sin previas en el orden de la entrada y usa una cola; cada tarea, al salir, libera a las que dependen de ella (en el orden en que se declararon). Si no se pueden ordenar todas, «Error: hay dependencias circulares». Después calcula el inicio más temprano (el máximo fin de sus previas) y el fin de cada tarea, la duración del proyecto, el fin más tardío de cada tarea sin retrasar el proyecto (recorriendo el orden al revés) y la holgura (fin tardío − fin temprano). Muestra el orden, una línea por tarea con «%-3s inicio %2d fin %2d holgura %d», la duración y el camino crítico: las tareas con holgura 0, en orden topológico, unidas por « → ».

Código de partida (java)
import java.util.*; public class Main {    record Tarea(String id, int duracion, List<String> previas) { }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        Map<String, Tarea> tareas = new LinkedHashMap<>();        while (sc.hasNextLine()) {            String l = sc.nextLine().trim();            if (l.isEmpty()) continue;            // Formato: ID duración [previa1,previa2,...]            String[] p = l.split("\\s+");            List<String> previas = p.length > 2 ? List.of(p[2].split(",")) : List.of();            tareas.put(p[0], new Tarea(p[0], Integer.parseInt(p[1]), previas));        }        // TODO: orden topológico, inicio y fin más tempranos, duración del proyecto,        //       holgura de cada tarea y camino crítico (o error si hay un ciclo)    }}

Ejemplo: Proyecto web

Entrada

A 3
B 2 A
C 4 A
D 1 B,C
E 2 D

Salida esperada

Orden: A B C D E
A   inicio  0  fin  3  holgura 0
B   inicio  3  fin  5  holgura 2
C   inicio  3  fin  7  holgura 0
D   inicio  7  fin  8  holgura 0
E   inicio  8  fin 10  holgura 0
Duración del proyecto: 10 días
Camino crítico: A → C → D → E

Ejemplo: Dos ramas

Entrada

Analisis 5
BD 3 Analisis
API 6 BD
UI 4 Analisis
Pruebas 2 API,UI
Manual 1 UI

Salida esperada

Orden: Analisis BD UI API Manual Pruebas
Analisis inicio  0  fin  5  holgura 0
BD  inicio  5  fin  8  holgura 0
UI  inicio  5  fin  9  holgura 5
API inicio  8  fin 14  holgura 0
Manual inicio  9  fin 10  holgura 6
Pruebas inicio 14  fin 16  holgura 0
Duración del proyecto: 16 días
Camino crítico: Analisis → BD → API → Pruebas

Instalación y uso de IDEs

3. Depura el resumen de notas

Difícil · Instalación y uso de IDEs · apuntesdam.com/subject/entornos/topic/ides

El método resumen debería calcular la media (con decimales), cuántas notas son aprobados (5 o más), la nota máxima y la mínima de un grupo de notas enteras de 0 a 10. Compila y parece funcionar, pero tiene cuatro errores. Usa el depurador de tu IDE (pon un punto de ruptura dentro del bucle y observa i, suma, max y min en cada vuelta) o añade trazas temporales, encuentra los cuatro y corrígelos sin reescribir el método desde cero.

Código de partida (java)
import java.util.*; public class Main {    // Este código compila pero tiene CUATRO errores. Encuéntralos con el depurador (o con trazas) y corrígelos.    static String resumen(int[] notas) {        int suma = 0, aprobados = 0, max = 0, min = 0;        for (int i = 1; i < notas.length; i++) {            suma += notas[i];            if (notas[i] > 5) aprobados++;            if (notas[i] > max) max = notas[i];            if (notas[i] < min) min = notas[i];        }        double media = suma / notas.length;        return String.format(Locale.US, "media %.2f, aprobados %d de %d, máxima %d, mínima %d",                media, aprobados, notas.length, max, min);    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextLine()) {            String l = sc.nextLine().trim();            if (l.isEmpty()) continue;            int[] notas = Arrays.stream(l.split("\\s+")).mapToInt(Integer::parseInt).toArray();            System.out.println(Arrays.toString(notas) + " → " + resumen(notas));        }    }}

Ejemplo: Grupo variado

Entrada

7 4 9 5 10

Salida esperada

[7, 4, 9, 5, 10] → media 7.00, aprobados 4 de 5, máxima 10, mínima 4

Ejemplo: Todos suspensos

Entrada

3 1 4

Salida esperada

[3, 1, 4] → media 2.67, aprobados 0 de 3, máxima 4, mínima 1

4. Depura una búsqueda binaria

Muy difícil · Instalación y uso de IDEs · apuntesdam.com/subject/entornos/topic/ides

primeraMayorOIgual debe devolver la primera posición de un array ordenado cuyo valor es mayor o igual que x, o a.length si todos son menores (es el «lower bound» que usan Arrays.binarySearch y las bases de datos en sus índices). Con ella, contar calcula cuántas veces aparece x. La versión actual tiene varios errores: según el caso, se queda en un bucle infinito, devuelve una posición equivocada o nunca devuelve a.length. Depúrala con casos pequeños (arrays de 1 o 2 elementos), anotando lo, hi y medio en cada vuelta, y corrígela. Además, el cálculo de medio puede desbordarse con arrays de más de mil millones de elementos: cámbialo por una forma segura.

Código de partida (java)
import java.util.*; public class Main {    // Primera posición i tal que a[i] >= x (a.length si no hay ninguna). El array está ordenado.    // Tiene errores que solo aparecen en algunos casos: encuéntralos.    static int primeraMayorOIgual(int[] a, int x) {        int lo = 0, hi = a.length - 1;        int vueltas = 0;                           // protección para que no se cuelgue mientras lo depuras        while (lo < hi && vueltas++ < 100) {            int medio = (lo + hi) / 2;            if (a[medio] < x) lo = medio;            else hi = medio - 1;        }        return lo;    }     static int contar(int[] a, int x) {        return primeraMayorOIgual(a, x + 1) - primeraMayorOIgual(a, x);    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int[] a = Arrays.stream(sc.nextLine().trim().split("\\s+")).mapToInt(Integer::parseInt).toArray();        while (sc.hasNextInt()) {            int x = sc.nextInt();            System.out.println("x=" + x + ": primera posición >= x → " + primeraMayorOIgual(a, x) + ", apariciones → " + contar(a, x));        }    }}

Ejemplo: Con repetidos

Entrada

1 3 3 3 5 8
3
4
0
9

Salida esperada

x=3: primera posición >= x → 1, apariciones → 3
x=4: primera posición >= x → 4, apariciones → 0
x=0: primera posición >= x → 0, apariciones → 0
x=9: primera posición >= x → 6, apariciones → 0

Ejemplo: Arrays pequeños

Entrada

7
7
8
6

Salida esperada

x=7: primera posición >= x → 0, apariciones → 1
x=8: primera posición >= x → 1, apariciones → 0
x=6: primera posición >= x → 0, apariciones → 0

Diseño y realización de pruebas

5. Valores límite: casos que detectan errores

Difícil · Diseño y realización de pruebas · apuntesdam.com/subject/entornos/topic/diseno-pruebas

La técnica de valores límite dice que los errores se esconden en las fronteras entre clases de equivalencia. El método tarifa ya está bien hecho; tu trabajo es escribir en CASOS las pruebas, cada una con un peso y la tarifa esperada. El programa comprueba que tus casos pasan con el método correcto y después los ejecuta contra seis versiones con un error cada una (mutantes), como los que comete cualquier programador: un < en vez de <=, un precio equivocado o un límite olvidado. Un mutante queda «detectado» si alguno de tus casos da un resultado distinto. Consigue detectar los seis con casos razonados, no al azar.

Código de partida (java)
import java.util.*;import java.util.function.DoubleUnaryOperator; public class Main {    // Tarifa de envío según el peso en kg. -1 significa «no admitido».    //   peso <= 0        → -1    //   0 < peso <= 1    → 4.5    //   1 < peso <= 5    → 7    //   5 < peso <= 20   → 12    //   peso > 20        → -1    static double tarifa(double peso) {        if (peso <= 0) return -1;        if (peso <= 1) return 4.5;        if (peso <= 5) return 7;        if (peso <= 20) return 12;        return -1;    }     // TODO: escribe aquí tus casos de prueba: {peso, resultado esperado}    static final double[][] CASOS = {        {3, 7},    };     // ---- No modifiques lo que sigue: versiones con un error cada una («mutantes»)    static final Map<String, DoubleUnaryOperator> MUTANTES = new LinkedHashMap<>();    static {        MUTANTES.put("M1 (< 1 en vez de <= 1)", p -> p <= 0 ? -1 : p < 1 ? 4.5 : p <= 5 ? 7 : p <= 20 ? 12 : -1);        MUTANTES.put("M2 (< 5 en vez de <= 5)", p -> p <= 0 ? -1 : p <= 1 ? 4.5 : p < 5 ? 7 : p <= 20 ? 12 : -1);        MUTANTES.put("M3 (< 20 en vez de <= 20)", p -> p <= 0 ? -1 : p <= 1 ? 4.5 : p <= 5 ? 7 : p < 20 ? 12 : -1);        MUTANTES.put("M4 (< 0 en vez de <= 0)", p -> p < 0 ? -1 : p <= 1 ? 4.5 : p <= 5 ? 7 : p <= 20 ? 12 : -1);        MUTANTES.put("M5 (sin límite superior)", p -> p <= 0 ? -1 : p <= 1 ? 4.5 : p <= 5 ? 7 : 12);        MUTANTES.put("M6 (7.5 en vez de 7)", p -> p <= 0 ? -1 : p <= 1 ? 4.5 : p <= 5 ? 7.5 : p <= 20 ? 12 : -1);    }     public static void main(String[] args) {        String fallo = null;        for (double[] c : CASOS)            if (tarifa(c[0]) != c[1] && fallo == null) fallo = "tarifa(" + c[0] + ") da " + tarifa(c[0]) + ", no " + c[1];        System.out.println("Casos correctos con el original: " + (fallo == null ? "sí" : "no (" + fallo + ")"));        List<String> vivos = new ArrayList<>();        for (var m : MUTANTES.entrySet()) {            boolean detectado = false;            for (double[] c : CASOS) if (m.getValue().applyAsDouble(c[0]) != c[1]) detectado = true;            if (!detectado) vivos.add(m.getKey());        }        System.out.println("Mutantes detectados: " + (MUTANTES.size() - vivos.size()) + " de " + MUTANTES.size());        System.out.println("Sobreviven: " + (vivos.isEmpty() ? "ninguno" : String.join(", ", vivos)));    }}

Ejemplo: Detección de mutantes

Salida esperada

Casos correctos con el original: sí
Mutantes detectados: 6 de 6
Sobreviven: ninguno

6. Matar mutantes con varias condiciones

Muy difícil · Diseño y realización de pruebas · apuntesdam.com/subject/entornos/topic/diseno-pruebas

El precio de la entrada de un museo depende de la edad, de si se es socio y de si es fin de semana (las reglas están en el comentario de ORIGINAL). Escribe en CASOS las pruebas necesarias, cada una con la edad, si es socio (1/0), si es fin de semana (1/0) y el precio esperado, para detectar los mutantes: errores en los límites de edad, en el orden en que se aplican los descuentos, en el tope de 0 € o en cómo se combinan las condiciones. Atención: uno de los ocho es un mutante equivalente, que se comporta exactamente igual que el original con cualquier entrada posible, así que ningún caso puede detectarlo. Detecta los otros siete y averigua cuál es el equivalente y por qué. Con muchas condiciones, probar todas las combinaciones crece muy deprisa: busca un conjunto pequeño (con unos 12 casos basta) en el que cada condición y cada límite influyan en algún resultado.

Código de partida (java)
import java.util.*; public class Main {    interface Precio { double calcular(int edad, boolean socio, boolean finDeSemana); }     // Precio de la entrada a un museo:    //   edad < 0 → -1 (error)    //   base 10 €; los fines de semana, 12 €    //   menores de 12: 50 % de descuento; desde 65: 40 % de descuento    //   los socios tienen además 2 € menos sobre el precio ya descontado (nunca por debajo de 0)    static final Precio ORIGINAL = (edad, socio, finde) -> {        if (edad < 0) return -1;        double p = finde ? 12 : 10;        if (edad < 12) p *= 0.5;        else if (edad >= 65) p *= 0.6;        if (socio) p = Math.max(0, p - 2);        return p;    };     // TODO: tus casos: {edad, socio (0/1), fin de semana (0/1), precio esperado}    static final double[][] CASOS = {        {30, 0, 0, 10},    };     // ---- No modifiques lo que sigue    static final Map<String, Precio> MUTANTES = new LinkedHashMap<>();    static {        MUTANTES.put("M1 (<= 12)", (e, s, f) -> e < 0 ? -1 : aplicar(f ? 12 : 10, e <= 12, e >= 65, s));        MUTANTES.put("M2 (> 65)", (e, s, f) -> e < 0 ? -1 : aplicar(f ? 12 : 10, e < 12, e > 65, s));        MUTANTES.put("M3 (<= 0 es error)", (e, s, f) -> e <= 0 ? -1 : aplicar(f ? 12 : 10, e < 12, e >= 65, s));        MUTANTES.put("M4 (socio antes del %)", (e, s, f) -> {            if (e < 0) return -1;            double p = (f ? 12 : 10) - (s ? 2 : 0);            return e < 12 ? p * 0.5 : e >= 65 ? p * 0.6 : p;        });        MUTANTES.put("M5 (sin tope en 0)", (e, s, f) -> {            if (e < 0) return -1;            double p = f ? 12 : 10;            if (e < 12) p *= 0.5; else if (e >= 65) p *= 0.6;            return s ? p - 2 : p;        });        MUTANTES.put("M6 (ignora el fin de semana con descuento)", (e, s, f) -> e < 0 ? -1 : aplicar((f && e >= 12 && e < 65) ? 12 : 10, e < 12, e >= 65, s));        MUTANTES.put("M7 (el socio solo descuenta a adultos)", (e, s, f) -> e < 0 ? -1 : aplicar(f ? 12 : 10, e < 12, e >= 65, s && e >= 12 && e < 65));        MUTANTES.put("M8 (65 o más: 50 %)", (e, s, f) -> {            if (e < 0) return -1;            double p = f ? 12 : 10;            if (e < 12 || e >= 65) p *= 0.5;            if (s) p = Math.max(0, p - 2);            return p;        });    }     static double aplicar(double p, boolean nino, boolean mayor, boolean socio) {        if (nino) p *= 0.5; else if (mayor) p *= 0.6;        if (socio) p = Math.max(0, p - 2);        return p;    }     static boolean igual(double a, double b) { return Math.abs(a - b) < 1e-9; }     public static void main(String[] args) {        String fallo = null;        for (double[] c : CASOS) {            double r = ORIGINAL.calcular((int) c[0], c[1] == 1, c[2] == 1);            if (!igual(r, c[3]) && fallo == null) fallo = Arrays.toString(c) + " da " + r;        }        System.out.println("Casos correctos con el original: " + (fallo == null ? "sí" : "no (" + fallo + ")"));        List<String> vivos = new ArrayList<>();        for (var m : MUTANTES.entrySet()) {            boolean detectado = false;            for (double[] c : CASOS)                if (!igual(m.getValue().calcular((int) c[0], c[1] == 1, c[2] == 1), c[3])) detectado = true;            if (!detectado) vivos.add(m.getKey());        }        System.out.println("Mutantes detectados: " + (MUTANTES.size() - vivos.size()) + " de " + MUTANTES.size());        System.out.println("Sobreviven: " + (vivos.isEmpty() ? "ninguno" : String.join(", ", vivos)));    }}

Ejemplo: Detección de mutantes

Salida esperada

Casos correctos con el original: sí
Mutantes detectados: 7 de 8
Sobreviven: M5 (sin tope en 0)

Optimización y Documentación

7. Un analizador de código en miniatura

Difícil · Optimización y Documentación · apuntesdam.com/subject/entornos/topic/optimizacion-documentacion

Herramientas como Checkstyle o PMD leen el código fuente y avisan de los «malos olores». Implementa analizar con cuatro reglas, recorriendo las líneas en orden. Detecta la cabecera de un método con la expresión regular METODO (el grupo 2 es el nombre y el 3, los parámetros). Regla 1: si el nombre no sigue lowerCamelCase (empieza por minúscula y solo tiene letras y cifras), «Línea n: el método «X» no sigue lowerCamelCase». Regla 2: si tiene más de MAX_PARAMETROS parámetros, «Línea n: «X» tiene p parámetros (máximo 3)». Regla 3: en cada línea que no declare una constante (que no contenga «final»), cada número distinto de 0 y 1 que no esté dentro de una cadena ni de un comentario // es «Línea n: número mágico N». Regla 4: un método que, desde su cabecera hasta la llave que lo cierra (cuenta { y }), mide más de MAX_LINEAS_METODO líneas da «Línea inicio: «X» mide L líneas (máximo 8)», y este aviso se añade al encontrar la llave de cierre. El main muestra los avisos y el total.

Código de partida (java)
import java.util.*;import java.util.regex.*; public class Main {    static final int MAX_LINEAS_METODO = 8, MAX_PARAMETROS = 3;    static final Pattern METODO = Pattern.compile(            "^\\s*(public|private|protected|static|\\s)+[\\w<>\\[\\]]+\\s+(\\w+)\\s*\\(([^)]*)\\)\\s*\\{");     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        List<String> lineas = new ArrayList<>();        while (sc.hasNextLine()) lineas.add(sc.nextLine());        List<String> avisos = analizar(lineas);        avisos.forEach(System.out::println);        System.out.println(avisos.size() + (avisos.size() == 1 ? " aviso" : " avisos"));    }     static List<String> analizar(List<String> lineas) {        List<String> avisos = new ArrayList<>();        // TODO: recorrer las líneas y aplicar las cuatro reglas        return avisos;    }}

Ejemplo: Nombre y número mágico

Entrada

public class Factura {
    static final double IVA = 0.21;

    public double total(double base) {
        return base * (1 + IVA);
    }

    public double Descuento(double total, int unidades) {
        if (unidades > 10) return total * 0.95;
        return total;
    }
}

Salida esperada

Línea 8: el método «Descuento» no sigue lowerCamelCase
Línea 9: número mágico 10
Línea 9: número mágico 0.95
3 avisos

Ejemplo: Método largo con muchos parámetros

Entrada

public class Pedidos {
    private double calcular(String cliente, int unidades, double precio, boolean urgente, String pais) {
        double total = unidades * precio;
        if (urgente) {
            total += 15;
        }
        if (pais.equals("ES")) {
            total *= 1.21;
        }
        String msg = "Pedido de 99 unidades";
        return total;
    }

    public static void main(String[] args) {
        System.out.println(new Pedidos().calcular("ana", 3, 2.5, true, "ES"));
    }
}

Salida esperada

Línea 2: «calcular» tiene 5 parámetros (máximo 3)
Línea 5: número mágico 15
Línea 8: número mágico 1.21
Línea 2: «calcular» mide 11 líneas (máximo 8)
Línea 15: número mágico 3
Línea 15: número mágico 2.5
6 avisos

8. Un Git en miniatura: base de fusión

Muy difícil · Optimización y Documentación · apuntesdam.com/subject/entornos/topic/optimizacion-documentacion

En Git, cada commit apunta a sus padres (uno normalmente, dos si es una fusión) y una rama es solo un nombre que apunta a un commit. Para fusionar, Git busca la base de fusión: el antepasado común más reciente de los dos commits. Implementa antepasados (el propio commit y todos los que se alcanzan siguiendo padres) y baseDeFusion: de los antepasados comunes, el que no es antepasado de ningún otro común (si hubiera varios, el primero en orden de creación; si no hay ninguno, null). Con eso, el main decide qué haría git merge: «ya actualizado» si el origen es la base, «fast-forward» si el destino es la base (basta con mover la rama) o «fusión de tres vías» en otro caso.

Código de partida (java)
import java.util.*; public class Main {    static final Map<String, List<String>> padres = new LinkedHashMap<>();   // commit → sus padres    static final Map<String, String> ramas = new TreeMap<>();                // rama → commit al que apunta     // Todos los antepasados de un commit, incluido él mismo    static Set<String> antepasados(String c) {        // TODO        return new HashSet<>();    }     // Antepasado común «más reciente» (el que no es antepasado de otro antepasado común); null si no hay    static String baseDeFusion(String a, String b) {        // TODO        return null;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextLine()) {            String[] t = sc.nextLine().trim().split("\\s+");            switch (t[0]) {                case "COMMIT" -> padres.put(t[1], t.length > 2 ? List.of(t[2].split(",")) : List.of());                case "RAMA" -> ramas.put(t[1], t[2]);                case "BASE" -> System.out.println("merge-base " + t[1] + " " + t[2] + " → " + baseDeFusion(ramas.get(t[1]), ramas.get(t[2])));                case "FUSIONAR" -> {        // FUSIONAR destino origen: ¿qué haría git merge origen estando en destino?                    String d = ramas.get(t[1]), o = ramas.get(t[2]);                    String base = baseDeFusion(d, o);                    String r;                    if (o.equals(base)) r = "ya actualizado (nada que fusionar)";                    else if (d.equals(base)) r = "fast-forward: " + t[1] + " pasa de " + d + " a " + o;                    else r = "fusión de tres vías (base " + base + "): se crea un commit de fusión";                    System.out.println("git merge " + t[2] + " en " + t[1] + " → " + r);                }            }        }    }}

Ejemplo: Ramas divergentes

Entrada

COMMIT c1
COMMIT c2 c1
COMMIT c3 c2
COMMIT c4 c3
COMMIT c5 c2
COMMIT c6 c5
RAMA main c4
RAMA login c6
RAMA hotfix c3
BASE main login
FUSIONAR main login
FUSIONAR main hotfix
FUSIONAR hotfix main

Salida esperada

merge-base main login → c2
git merge login en main → fusión de tres vías (base c2): se crea un commit de fusión
git merge hotfix en main → ya actualizado (nada que fusionar)
git merge main en hotfix → fast-forward: hotfix pasa de c3 a c4

Ejemplo: Después de fusionar

Entrada

COMMIT c1
COMMIT c2 c1
COMMIT c3 c2
COMMIT c4 c3
COMMIT c5 c2
COMMIT c6 c5
RAMA main c4
RAMA login c6
RAMA hotfix c3
COMMIT c7 c4,c6
RAMA main c7
BASE main login
FUSIONAR main login
FUSIONAR login main

Salida esperada

merge-base main login → c6
git merge login en main → ya actualizado (nada que fusionar)
git merge main en login → fast-forward: login pasa de c6 a c7

Pruebas con JUnit y Mockito

9. Dobles de prueba escritos a mano

Difícil · Pruebas con JUnit y Mockito · apuntesdam.com/subject/entornos/topic/pruebas-junit

Para probar ServicioPedidos sin una base de datos ni un servidor de correo reales se usan dobles de prueba: objetos que implementan las mismas interfaces. Completa InventarioFalso, cuyo constructor recibe el stock que devolverá stock() para cualquier producto y que anota cada llamada a descontar como «producto -unidades» en una lista que devuelve descuentos(), y CorreoEspia, que no envía nada: guarda cada envío como «destinatario | asunto» en una lista que devuelve enviados(). Es lo que hace Mockito automáticamente con when(...).thenReturn(...) y verify(...). El main ya prueba el servicio con distintos casos y muestra lo que han registrado tus dobles.

Código de partida (java)
import java.util.*; interface Inventario {    int stock(String producto);    void descontar(String producto, int unidades);} interface Correo {    void enviar(String destinatario, String asunto);} // Clase que se quiere probar: no sabe si habla con una base de datos real o con un dobleclass ServicioPedidos {    private final Inventario inventario;    private final Correo correo;     ServicioPedidos(Inventario inventario, Correo correo) {        this.inventario = inventario;        this.correo = correo;    }     boolean pedir(String cliente, String producto, int unidades) {        if (unidades <= 0) return false;        if (inventario.stock(producto) < unidades) {            correo.enviar("almacen@tienda.es", "Sin stock: " + producto);            return false;        }        inventario.descontar(producto, unidades);        correo.enviar(cliente, "Pedido confirmado: " + unidades + " x " + producto);        return true;    }} // TODO: un doble de Inventario con un stock fijado por la prueba que anote las llamadas a descontarclass InventarioFalso implements Inventario {    InventarioFalso(int stockFijo) { }    public int stock(String producto) { return 0; }    public void descontar(String producto, int unidades) { }    List<String> descuentos() { return List.of(); }} // TODO: un doble de Correo que guarde cada envío como "destinatario | asunto"class CorreoEspia implements Correo {    public void enviar(String destinatario, String asunto) { }    List<String> enviados() { return List.of(); }} public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextLine()) {            String[] t = sc.nextLine().trim().split(" ");   // stock cliente producto unidades            InventarioFalso inv = new InventarioFalso(Integer.parseInt(t[0]));            CorreoEspia correo = new CorreoEspia();            boolean ok = new ServicioPedidos(inv, correo).pedir(t[1], t[2], Integer.parseInt(t[3]));            System.out.println("pedir(" + t[1] + ", " + t[2] + ", " + t[3] + ") con stock " + t[0] + " → " + ok);            System.out.println("  descuentos: " + inv.descuentos());            System.out.println("  correos: " + correo.enviados());        }    }}

Ejemplo: Pedido correcto

Entrada

10 ana@mail.com teclado 3

Salida esperada

pedir(ana@mail.com, teclado, 3) con stock 10 → true
  descuentos: [teclado -3]
  correos: [ana@mail.com | Pedido confirmado: 3 x teclado]

Ejemplo: Sin stock y unidades no válidas

Entrada

2 luis@mail.com raton 5
5 eva@mail.com monitor 0

Salida esperada

pedir(luis@mail.com, raton, 5) con stock 2 → false
  descuentos: []
  correos: [almacen@tienda.es | Sin stock: raton]
pedir(eva@mail.com, monitor, 0) con stock 5 → false
  descuentos: []
  correos: []

10. Tu propio JUnit con reflexión

Muy difícil · Pruebas con JUnit y Mockito · apuntesdam.com/subject/entornos/topic/pruebas-junit

¿Cómo encuentra JUnit los métodos de prueba? Con reflexión: inspecciona la clase en tiempo de ejecución y busca los métodos con la anotación @Test. Implementa ejecutar: localiza los métodos anotados con @Prueba y @Antes de la clase, ordena las pruebas por nombre y, para cada una, crea una instancia nueva de la clase (así una prueba no afecta a otra), ejecuta los métodos @Antes y después la prueba. Si termina bien, «✔ nombre»; si lanza FalloAsercion, es un fallo: «✘ nombre: mensaje»; si lanza cualquier otra excepción, es un error: «‼ nombre: NombreSimpleDeLaExcepción». Al final, «Pruebas: N, correctas: a, fallidas: b, con error: c». Los métodos sin anotación no se ejecutan.

Código de partida (java)
import java.lang.annotation.*;import java.lang.reflect.*;import java.util.*; // Anotaciones propias, como @Test y @BeforeEach de JUnit@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Prueba { }@Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @interface Antes { } class FalloAsercion extends RuntimeException {    FalloAsercion(String m) { super(m); }} class Afirmar {    static void iguales(Object esperado, Object real) {        if (!Objects.equals(esperado, real)) throw new FalloAsercion("se esperaba <" + esperado + "> pero fue <" + real + ">");    }    static void cierto(boolean c, String mensaje) {        if (!c) throw new FalloAsercion(mensaje);    }} // ---- Las pruebas de ejemplo (no las modifiques)class PilaTest {    private Deque<Integer> pila;     @Antes void preparar() { pila = new ArrayDeque<>(); pila.push(1); pila.push(2); }     @Prueba void cimaEsElUltimo() { Afirmar.iguales(2, pila.peek()); }    @Prueba void sacarReduce() { pila.pop(); Afirmar.iguales(1, pila.size()); }    @Prueba void cadaPruebaEmpiezaLimpia() { Afirmar.iguales(2, pila.size()); }    @Prueba void fallaAProposito() { Afirmar.iguales(3, pila.size()); }    @Prueba void lanzaExcepcion() { new ArrayDeque<Integer>().pop(); }    void noEsUnaPrueba() { throw new IllegalStateException("no debería ejecutarse"); }} public class Main {    // Ejecuta todos los métodos @Prueba de la clase, en orden alfabético, con una instancia nueva cada vez    static void ejecutar(Class<?> clase) throws Exception {        // TODO    }     public static void main(String[] args) throws Exception {        ejecutar(PilaTest.class);    }}

Ejemplo: Pila de ejemplo

Salida esperada

✔ cadaPruebaEmpiezaLimpia
✔ cimaEsElUltimo
✘ fallaAProposito: se esperaba <3> pero fue <2>
‼ lanzaExcepcion: NoSuchElementException
✔ sacarReduce
Pruebas: 5, correctas: 3, fallidas: 1, con error: 1

Diagramas UML

11. Del diagrama de estados al código

Difícil · Diagramas UML · apuntesdam.com/subject/entornos/topic/diagramas-uml

El comentario de la clase describe el diagrama de estados de un pedido: sus estados, los eventos que provocan cada transición y las acciones asociadas. Implementa aplicar para que, según el estado actual y el evento, cambie de estado y devuelva el nuevo estado con su acción entre corchetes si la tiene («CANCELADO [devolver el dinero]», «PAGADO [reenviar]»). Un paquete perdido vuelve a PAGADO para reenviarlo, pero a la tercera pérdida el pedido pasa a CANCELADO con devolución. Cualquier evento que no tenga una transición desde el estado actual (incluidos todos los eventos en los estados finales) devuelve «transición no permitida (sigue en ESTADO)» sin cambiar nada.

Código de partida (java)
import java.util.*; public class Main {    // Diagrama de estados de un pedido (estado inicial: CARRITO)    //   CARRITO   --pagar-->     PAGADO    //   CARRITO   --cancelar-->  CANCELADO    //   PAGADO    --enviar-->    ENVIADO    //   PAGADO    --cancelar-->  CANCELADO   [acción: devolver el dinero]    //   ENVIADO   --entregar-->  ENTREGADO    //   ENVIADO   --perder-->    PAGADO      [acción: reenviar; como mucho 2 veces, después CANCELADO con devolución]    //   ENTREGADO y CANCELADO son estados finales    enum Estado { CARRITO, PAGADO, ENVIADO, ENTREGADO, CANCELADO }     static Estado estado = Estado.CARRITO;    static int perdidas = 0;     // Aplica un evento y devuelve el texto que describe lo ocurrido    static String aplicar(String evento) {        // TODO        return "";    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNext()) {            String ev = sc.next();            Estado antes = estado;            String r = aplicar(ev);            System.out.println(antes + " --" + ev + "--> " + r);        }        System.out.println("Estado final: " + estado);    }}

Ejemplo: Camino feliz

Entrada

pagar enviar entregar

Salida esperada

CARRITO --pagar--> PAGADO
PAGADO --enviar--> ENVIADO
ENVIADO --entregar--> ENTREGADO
Estado final: ENTREGADO

Ejemplo: Eventos no válidos y cancelación

Entrada

enviar pagar cancelar entregar

Salida esperada

CARRITO --enviar--> transición no permitida (sigue en CARRITO)
CARRITO --pagar--> PAGADO
PAGADO --cancelar--> CANCELADO [devolver el dinero]
CANCELADO --entregar--> transición no permitida (sigue en CANCELADO)
Estado final: CANCELADO

12. Ingeniería inversa: de las clases al diagrama

Muy difícil · Diagramas UML · apuntesdam.com/subject/entornos/topic/diagramas-uml

Herramientas como IntelliJ o PlantUML pueden generar el diagrama de clases a partir del código. Hazlo tú con reflexión: para cada clase de la lista, en orden, escribe su bloque de PlantUML («interface X {», «abstract class X {» o «class X {»). Dentro, los atributos ordenados por nombre con su visibilidad (+ public, - private, # protected, ~ paquete) como «-dni : String», y después los métodos declarados en la clase, también por nombre, como «+matricular(Asignatura) : void». Un atributo cuyo tipo es otra clase del modelo no se escribe como atributo sino como asociación «Asignatura --> "1" Profesor : titular»; uno de tipo List<X> es una asociación con multiplicidad * («Alumno --> "*" Matricula : matriculas»). Al final, después de todos los bloques y en el orden en que se descubrieron mientras se recorrían las clases (primero las asociaciones de cada clase y luego su herencia), las relaciones, incluidas la herencia «Persona <|-- Alumno» y la realización de interfaces «Evaluable <|.. Alumno».

Código de partida (java)
import java.lang.reflect.*;import java.util.*; // ---- Modelo de ejemplo (no lo modifiques)abstract class Persona {    protected String nombre;    private String dni;    public String getNombre() { return nombre; }}interface Evaluable { double nota(); }class Alumno extends Persona implements Evaluable {    private int curso;    private List<Matricula> matriculas;    public double nota() { return 0; }    public void matricular(Asignatura a) { }}class Profesor extends Persona {    private double salario;    private List<Asignatura> imparte;}class Asignatura {    private String codigo;    int horas;    private Profesor titular;}class Matricula {    private Asignatura asignatura;    private double nota;} public class Main {    static final List<Class<?>> MODELO = List.of(Persona.class, Evaluable.class, Alumno.class, Profesor.class, Asignatura.class, Matricula.class);     static String visibilidad(int mod) {        return Modifier.isPublic(mod) ? "+" : Modifier.isPrivate(mod) ? "-" : Modifier.isProtected(mod) ? "#" : "~";    }     // Genera el diagrama de clases en PlantUML a partir de las clases compiladas    static String plantUml(List<Class<?>> clases) {        StringBuilder sb = new StringBuilder("@startuml\n");        // TODO        return sb.append("@enduml").toString();    }     public static void main(String[] args) {        System.out.println(plantUml(MODELO));    }}

Ejemplo: Modelo de un instituto

Salida esperada

@startuml
abstract class Persona {
  -dni : String
  #nombre : String
  +getNombre() : String
}
interface Evaluable {
  +nota() : double
}
class Alumno {
  -curso : int
  +matricular(Asignatura) : void
  +nota() : double
}
class Profesor {
  -salario : double
}
class Asignatura {
  -codigo : String
  ~horas : int
}
class Matricula {
  -nota : double
}
Alumno --> "*" Matricula : matriculas
Persona <|-- Alumno
Evaluable <|.. Alumno
Profesor --> "*" Asignatura : imparte
Persona <|-- Profesor
Asignatura --> "1" Profesor : titular
Matricula --> "1" Asignatura : asignatura
@enduml

Ejercicios de Entornos de Desarrollo

13. Depura el programa de notas

Fácil · Ejercicios de Entornos de Desarrollo · apuntesdam.com/subject/entornos/topic/ejercicios-entornos

Este programa lee N notas y debe mostrar su media con dos decimales y la nota máxima, pero tiene tres errores. Usa el depurador (o trazas) para encontrarlos y corrígelos sin reescribir el programa. Entrada: N y después las N notas.

Código de partida (java)
import java.util.*; public class Main {     // Devuelve la media de las notas (con decimales)    static double media(int[] notas) {        int suma = 0;        for (int i = 1; i <= notas.length - 1; i++) {            suma += notas[i];        }        return suma / notas.length;    }     // Devuelve la nota más alta    static int maxima(int[] notas) {        int max = notas[0];        for (int n : notas) {            if (n < max) max = n;        }        return max;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        int n = sc.nextInt();        int[] notas = new int[n];        for (int i = 0; i < n; i++) notas[i] = sc.nextInt();        System.out.printf(Locale.US, "Media: %.2f%n", media(notas));        System.out.println("Máxima: " + maxima(notas));    }}

Ejemplo: Cuatro notas

Entrada

4
5 7 8 10

Salida esperada

Media: 7.50
Máxima: 10

Ejemplo: Media con decimales

Entrada

3
6 6 7

Salida esperada

Media: 6.33
Máxima: 7

14. Valores límite: clasificar edades

Fácil · Ejercicios de Entornos de Desarrollo · apuntesdam.com/subject/entornos/topic/ejercicios-entornos

Implementa clasificar(edad) según esta especificación: edad < 0 o > 120 → "Error"; 0–11 → "Niño"; 12–17 → "Adolescente"; 18–64 → "Adulto"; 65–120 → "Jubilado". Los tests usan justo los valores límite de cada clase de equivalencia, que es donde suelen esconderse los errores. Entrada: una edad por línea.

Código de partida (java)
import java.util.*; public class Main {     static String clasificar(int edad) {        // TODO: implementa la especificación        return "";    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextInt()) {            System.out.println(clasificar(sc.nextInt()));        }    }}

Ejemplo: Límites inferiores

Entrada

-1
0
11
12

Salida esperada

Error
Niño
Niño
Adolescente

Ejemplo: Límites centrales

Entrada

17
18
64
65

Salida esperada

Adolescente
Adulto
Adulto
Jubilado

15. Caja negra: el problema del triángulo

Medio · Ejercicios de Entornos de Desarrollo · apuntesdam.com/subject/entornos/topic/ejercicios-entornos

Clásico de las pruebas de caja negra. Implementa tipo(a, b, c), que recibe las longitudes de tres lados enteros y devuelve "Equilátero", "Isósceles", "Escaleno" o "No es triángulo" (si algún lado es ≤ 0 o si un lado es mayor o igual que la suma de los otros dos). Entrada: tres enteros por línea.

Código de partida (java)
import java.util.*; public class Main {     static String tipo(int a, int b, int c) {        // TODO: clasifica el triángulo        return "";    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        while (sc.hasNextInt()) {            System.out.println(tipo(sc.nextInt(), sc.nextInt(), sc.nextInt()));        }    }}

Ejemplo: Tipos válidos

Entrada

3 3 3
3 3 5
3 4 5

Salida esperada

Equilátero
Isósceles
Escaleno

Ejemplo: Casos inválidos

Entrada

0 4 5
1 2 3
-3 4 5

Salida esperada

No es triángulo
No es triángulo
No es triángulo

16. Refactoriza: extraer método

Medio · Ejercicios de Entornos de Desarrollo · apuntesdam.com/subject/entornos/topic/ejercicios-entornos

El cálculo original del precio estaba en un único bloque lleno de números mágicos: p = base * unidades; si VIP p -= 15 %; si SOCIO p -= 5 %; si unidades >= 10 p -= 10 %; p += 21 % de IVA. Ya se ha extraído la estructura en métodos pequeños. Completa descuentoCliente(), descuentoVolumen() y el IVA de precioFinal() para que el comportamiento sea idéntico al original. Entrada por línea: precio base, unidades y tipo de cliente (NORMAL, SOCIO o VIP).

Código de partida (java)
import java.util.*; public class Main {     static final double IVA = 0.21;     static double descuentoCliente(String tipo) {        // TODO: 0.15 para VIP, 0.05 para SOCIO y 0 para el resto        return 0;    }     static double descuentoVolumen(int unidades) {        // TODO: 0.10 a partir de 10 unidades        return 0;    }     static double aplicarDescuento(double importe, double descuento) {        return importe * (1 - descuento);    }     static double precioFinal(double base, int unidades, String tipo) {        double importe = base * unidades;        importe = aplicarDescuento(importe, descuentoCliente(tipo));        importe = aplicarDescuento(importe, descuentoVolumen(unidades));        // TODO: aplica el IVA usando la constante        return importe;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in).useLocale(Locale.US);        while (sc.hasNext()) {            double base = sc.nextDouble();            int unidades = sc.nextInt();            String tipo = sc.next();            System.out.printf(Locale.US, "%.2f%n", precioFinal(base, unidades, tipo));        }    }}

Ejemplo: Cliente normal

Entrada

10 2 NORMAL

Salida esperada

24.20

Ejemplo: Socio con descuento por volumen

Entrada

20 12 SOCIO

Salida esperada

248.29

17. Del diagrama de clases al código

Medio · Ejercicios de Entornos de Desarrollo · apuntesdam.com/subject/entornos/topic/ejercicios-entornos

Implementa el diagrama de clases: la clase abstracta Figura (nombre, area() abstracto y toString() ya hechos) y sus subclases Circulo (radio) y Rectangulo (base, altura), que heredan de ella. El programa lee figuras (C radio o R base altura), las muestra y suma sus áreas. Figura ◁── Circulo (- radio: double) Figura ◁── Rectangulo (- base: double, - altura: double)

Código de partida (java)
import java.util.*; abstract class Figura {    protected String nombre;     public Figura(String nombre) { this.nombre = nombre; }     public abstract double area();     @Override    public String toString() {        return String.format(Locale.US, "%s: %.2f", nombre, area());    }} class Circulo extends Figura {    private double radio;     public Circulo(double radio) {        super("Círculo");        // TODO: guarda el radio    }     @Override    public double area() {        return 0; // TODO: π·r²    }} class Rectangulo extends Figura {    // TODO: atributos base y altura     public Rectangulo(double base, double altura) {        super("Rectángulo");        // TODO: guarda base y altura    }     @Override    public double area() {        return 0; // TODO: base·altura    }} public class Main {    public static void main(String[] args) {        Scanner sc = new Scanner(System.in).useLocale(Locale.US);        List<Figura> figuras = new ArrayList<>();        while (sc.hasNext()) {            String tipo = sc.next();            if (tipo.equals("C")) figuras.add(new Circulo(sc.nextDouble()));            else figuras.add(new Rectangulo(sc.nextDouble(), sc.nextDouble()));        }        double total = 0;        for (Figura f : figuras) {            System.out.println(f);            total += f.area();        }        System.out.printf(Locale.US, "Total: %.2f%n", total);    }}

Ejemplo: Un círculo y un rectángulo

Entrada

C 1
R 3 4

Salida esperada

Círculo: 3.14
Rectángulo: 12.00
Total: 15.14

Ejercicios largos

18. Validador de DNI, NIE e IBAN diseñado con clases de equivalencia

Medio · Java · 50 minutos · apuntesdam.com/ejercicios/java/validador-dni-nie-iban

Casi todas las aplicaciones de gestión piden un DNI, un NIE o un IBAN, y casi todas los validan mal: aceptan letras que no corresponden, rechazan minúsculas o se atascan con los espacios del IBAN. Los tres llevan un control matemático que permite detectar la mayoría de los errores al teclear.

La letra del DNI es el resto de dividir el número entre 23, buscado en la tabla TRWAGMYFPDXBNJZSQVHLCKE. El NIE de los extranjeros funciona igual cambiando la X, la Y y la Z iniciales por 0, 1 y 2. El IBAN usa otro método (ISO 13616): se pasan los cuatro primeros caracteres al final, cada letra se sustituye por un número (A = 10, B = 11… Z = 35) y el número resultante, de unas 26 cifras, tiene que dar resto 1 al dividirlo entre 97.

Este ejercicio es también de Entornos de Desarrollo: las pruebas no se han elegido al azar, sino con la técnica de clases de equivalencia (un caso representativo de cada grupo de entradas que el programa trata igual) y valores límite (los bordes de cada grupo). Tienes la tabla abajo: tu programa tiene que pasar todos esos casos.

Requisitos

  • Cada línea es TIPO valor, con TIPO DNI, NIE o IBAN en mayúsculas o minúsculas. Las líneas vacías se ignoran. Para cada línea escribe TIPO valor → válido o TIPO valor → no válido: motivo, con el tipo en mayúsculas y el valor tal como venía (sin espacios al principio ni al final).

  • DNI: 8 cifras y una letra (mayúscula o minúscula). Si no tiene ese formato, formato incorrecto (8 cifras y una letra); si la letra no es la que toca, la letra debería ser L.

  • NIE: X, Y o Z, 7 cifras y una letra. Si no tiene ese formato, formato incorrecto (X, Y o Z, 7 cifras y una letra); si la letra no cuadra, el mismo mensaje que el DNI.

  • IBAN (se ignoran los espacios y las minúsculas): si no empieza por ES, solo se admiten IBAN españoles; si no tiene 24 caracteres, un IBAN español tiene 24 caracteres; si tras ES hay algo que no sea una cifra, después de ES solo puede haber cifras; si el control módulo 97 no da 1, los dígitos de control no cuadran. Se comprueba en ese orden.

  • Un tipo desconocido escribe TIPO valor → no válido: tipo de documento desconocido.

  • Al final, Válidos: V de T.

Formato de la entrada

  • Una línea por documento: DNI valor, NIE valor o IBAN valor.

Clases de equivalencia y valores límite del DNI
ClaseCaso de pruebaResultado esperado
Válida: 8 cifras y su letra12345678Zválido
Válida: letra en minúscula12345678zválido
Límite inferior del número00000000Tválido
Límite superior del número99999999Rválido
No válida: letra que no toca12345678Ala letra debería ser Z
No válida: 7 cifras1234567Zformato incorrecto
No válida: 9 cifras123456789Zformato incorrecto
No válida: sin letra12345678formato incorrecto
Clases de equivalencia del IBAN
ClaseCaso de pruebaResultado esperado
Válida, con espaciosES91 2100 0418 4502 0005 1332válido
Válida, en minúsculas y sin espacioses9121000418450200051332válido
Otro paísDE89370400440532013000solo se admiten IBAN españoles
Longitud incorrectaES91210004184502000513324 caracteres
Letras tras ESES91 2100 0418 4502 0005 13ABsolo cifras
Control incorrecto (una cifra cambiada)ES91 2100 0418 4502 0005 1333no cuadran

Ejemplo: DNI y NIE

Entrada

DNI 12345678Z
DNI 12345678z
DNI 12345678A
DNI 1234567Z
NIE X1234567L
NIE W1234567L
PASAPORTE AB123456

Salida esperada

DNI 12345678Z → válido
DNI 12345678z → válido
DNI 12345678A → no válido: la letra debería ser Z
DNI 1234567Z → no válido: formato incorrecto (8 cifras y una letra)
NIE X1234567L → válido
NIE W1234567L → no válido: formato incorrecto (X, Y o Z, 7 cifras y una letra)
PASAPORTE AB123456 → no válido: tipo de documento desconocido
Válidos: 3 de 7

Ejemplo: IBAN

Entrada

IBAN ES91 2100 0418 4502 0005 1332
iban es9121000418450200051332
IBAN DE89370400440532013000
IBAN ES912100041845020005133
IBAN ES91 2100 0418 4502 0005 13AB
IBAN ES91 2100 0418 4502 0005 1333

Salida esperada

IBAN ES91 2100 0418 4502 0005 1332 → válido
IBAN es9121000418450200051332 → válido
IBAN DE89370400440532013000 → no válido: solo se admiten IBAN españoles
IBAN ES912100041845020005133 → no válido: un IBAN español tiene 24 caracteres
IBAN ES91 2100 0418 4502 0005 13AB → no válido: después de ES solo puede haber cifras
IBAN ES91 2100 0418 4502 0005 1333 → no válido: los dígitos de control no cuadran
Válidos: 2 de 6
Código de partida (java)
import java.util.Scanner; public class Main {    static final String LETRAS = "TRWAGMYFPDXBNJZSQVHLCKE";     /** null si el DNI es correcto, o el motivo por el que no lo es. */    static String validarDni(String dni) {        // TODO        return null;    }     /** El NIE se valida como un DNI cambiando X, Y y Z por 0, 1 y 2. */    static String validarNie(String nie) {        // TODO        return null;    }     /** IBAN español: 24 caracteres y control módulo 97 (ISO 13616). */    static String validarIban(String iban) {        // TODO        return null;    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        // TODO: valida cada línea y escribe el resultado y el resumen    }}

19. Depura el programa de notas: cinco errores escondidos

Medio · Java · 45 minutos · apuntesdam.com/ejercicios/java/depura-el-calculo-de-notas

Un compañero ha escrito el programa que calcula las notas finales de Programación y lo ha dejado para que lo revises antes de publicar las notas. Compila, no lanza excepciones y con su ejemplo de prueba (tres alumnos con notas «normales») el resultado parecía razonable. Pero varios alumnos se han quejado de que su nota no cuadra.

La regla de evaluación es: cada una de las dos prácticas vale un 20 % y el examen un 60 %; si el examen tiene menos de un 4, la nota final no puede pasar de 4 (el examen no se compensa), y la nota se redondea a un decimal. Aprueba quien tiene un 5 o más.

El código del editor tiene exactamente cinco errores, cada uno de una línea. No está permitido reescribir el programa: hay que encontrar cada error y corregirlo. Es lo que se hace en un proyecto real, donde reescribir lo que no se entiende introduce errores nuevos.

Requisitos

  • La entrada tiene una línea por alumno: nombre;práctica1;práctica2;examen, con notas de 0 a 10 con punto decimal.

  • Nota final = 0,2 × práctica1 + 0,2 × práctica2 + 0,6 × examen; si el examen es menor que 4, la nota final es como mucho 4; se redondea a un decimal (las mitades suben).

  • Se escribe una línea por alumno, de mayor a menor nota (a igualdad, por nombre): el nombre en 20 caracteres a la izquierda y la nota con un decimal y coma en 5 caracteres a la derecha.

  • Al final, Media: X (la media de las notas finales con un decimal) y Aprobados: A de T.

  • Corrige los cinco errores del programa sin cambiar su estructura. Las pruebas ocultas están pensadas para detectar cada uno.

Formato de la entrada

  • Una línea por alumno: nombre;práctica1;práctica2;examen.

Ejemplo: Una clase de cinco alumnos

Entrada

Ana Gil;8;9;7.5
Luis Mora;6;5;4
Eva Ruiz;10;10;3.5
Pablo Sanz;5;5;5
Marta Peña;7.5;6.5;6.2

Salida esperada

Ana Gil               7,9
Marta Peña            6,5
Pablo Sanz            5,0
Luis Mora             4,6
Eva Ruiz              4,0
Media: 5,6
Aprobados: 3 de 5

Ejemplo: El examen de 4 sí compensa

Entrada

Irene Vidal;9;9;4
Jorge Lara;3;3;3.9

Salida esperada

Irene Vidal           6,0
Jorge Lara            3,5
Media: 4,8
Aprobados: 1 de 2
Código de partida (java)
import java.util.ArrayList;import java.util.List;import java.util.Locale;import java.util.Scanner; public class Main {    record Alumno(String nombre, double nota) {}     /** Nota final: prácticas al 20 % cada una y examen al 60 %; con menos de un 4 en el examen, como mucho un 4. */    static double notaFinal(double practica1, double practica2, double examen) {        double nota = 0.2 * practica1 + 0.2 * practica1 + 0.6 * examen;        if (examen <= 4) nota = Math.min(nota, 4);        return Math.round(nota * 10) / 10;    }     static String uno(double x) {        return String.format(Locale.ROOT, "%.1f", x).replace('.', ',');    }     public static void main(String[] args) {        Scanner sc = new Scanner(System.in);        List<String> lineas = new ArrayList<>();        while (sc.hasNextLine()) {            String l = sc.nextLine().trim();            if (!l.isEmpty()) lineas.add(l);        }         List<Alumno> alumnos = new ArrayList<>();        for (int i = 1; i < lineas.size(); i++) {            String[] p = lineas.get(i).split(";");            alumnos.add(new Alumno(p[0], notaFinal(Double.parseDouble(p[1]), Double.parseDouble(p[2]), Double.parseDouble(p[3]))));        }        alumnos.sort((a, b) -> a.nota() != b.nota() ? Double.compare(b.nota(), a.nota()) : a.nombre().compareTo(b.nombre()));         double suma = 0;        int aprobados = 0;        for (Alumno a : alumnos) {            System.out.printf("%-20s%5s%n", a.nombre(), uno(a.nota()));            suma += a.nota();            if (a.nota() > 5) aprobados++;        }        System.out.println("Media: " + uno(suma / alumnos.size()));        System.out.println("Aprobados: " + aprobados + " de " + alumnos.size());    }}