Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Ejercicios: Testing, Variables Globales y Estructura de Código

Pruebas unitarias, eliminación de variables globales, análisis de flujo y buenas prácticas

Universidad Nacional de Río Negro

Acerca de

El testing y el diseño estructurado de código son disciplinas indispensables para asegurar la confiabilidad de los programas en C. En este bloque no solo vas a escribir código nuevo, sino también a leer, analizar, depurar y refactorizar programas existentes.

Aprenderás a identificar el impacto negativo de las variables globales mutables, diseñar suites de pruebas unitarias automatizadas con assert(), analizar la cobertura de ramas de ejecución y aplicar las reglas de estilo institucional (Allman, contratos y desacoplamiento de entrada/salida).

Capítulos de Apunte Correspondientes

Cuestiones de Estilo Aplicables


1. Conceptos Fundamentales de Testing y Aserciones

Ejercicio 4.1 - Anatomía de una aserción ⭐☆☆☆☆

Solution to Exercise 1
  1. Se incluye <assert.h>.

  2. Si la condición es true, el programa continúa su ejecución normal sin ninguna interrupción ni mensaje.

  3. Si la condición es false, la macro assert aborta inmediatamente el programa con SIGABRT e imprime en stderr el nombre del archivo, el número de línea, la función y el texto exacto de la condición que falló.


Ejercicio 4.2 - Patrón AAA (Arrange - Act - Assert) ⭐☆☆☆☆

Solution to Exercise 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void test_calcular_promedio_tres_notas(void)
{
    // 1. Arrange (Preparar): Definición de datos de entrada y resultado esperado
    int nota1 = 8;
    int nota2 = 6;
    int nota3 = 10;
    double esperado = 8.0;

    // 2. Act (Actuar): Invocación de la función bajo prueba
    double resultado = calcular_promedio(nota1, nota2, nota3);

    // 3. Assert (Verificar): Comprobación de que el resultado coincide con lo esperado
    assert(resultado == esperado);
}

Ejercicio 4.3 - Refactorización de pruebas acumuladas ⭐⭐☆☆☆

Solution to Exercise 3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <assert.h>

void test_sumar_positivos(void)
{
    assert(sumar(2, 3) == 5);
}

void test_sumar_opuestos(void)
{
    assert(sumar(-1, 1) == 0);
}

void test_restar_positivo_mayor(void)
{
    assert(restar(10, 4) == 6);
}

void test_restar_resultado_negativo(void)
{
    assert(restar(3, 8) == -5);
}

void test_multiplicar_positivos(void)
{
    assert(multiplicar(3, 4) == 12);
}

void correr_todas_las_pruebas(void)
{
    test_sumar_positivos();
    test_sumar_opuestos();
    test_restar_positivo_mayor();
    test_restar_resultado_negativo();
    test_multiplicar_positivos();
}

Ejercicio 4.4 - Efectos colaterales prohibidos dentro de assert ⭐⭐☆☆☆

Solution to Exercise 4

Cuando se define NDEBUG, el preprocesador reemplaza todas las macros assert(x) por nada (bloque vacío ((void)0)).

En consecuencia, las llamadas a procesar_siguiente_item(&idx) desaparecen por completo del código ejecutable, por lo que idx nunca se incrementa y el programa finaliza imprimiendo Índice final: 0 en lugar de 2.

Forma correcta:

int idx = 0;
int r1 = procesar_siguiente_item(&idx);
assert(r1 == 0);
int r2 = procesar_siguiente_item(&idx);
assert(r2 == 1);

Ejercicio 4.5 - Particiones de Equivalencia ⭐⭐☆☆☆

Solution to Exercise 5

Las particiones de equivalencia mínimas son:

  1. Inválida negativa: edad = -5 \rightarrow Esperado: -1

  2. Menor de edad: edad = 10 \rightarrow Esperado: 1

  3. Adulto: edad = 30 \rightarrow Esperado: 2

  4. Adulto mayor: edad = 70 \rightarrow Esperado: 3

  5. Inválida excesiva: edad = 150 \rightarrow Esperado: -1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <assert.h>

int clasificar_edad(int edad)
{
    if (edad < 0 || edad > 130)
    {
        return -1;
    }
    if (edad < 18)
    {
        return 1;
    }
    if (edad <= 64)
    {
        return 2;
    }
    return 3;
}

Ejercicio 4.6 - Análisis de Valores de Borde (Boundary Values) ⭐⭐☆☆☆

Solution to Exercise 6

Valores de borde críticos:

  • Límite inferior de validez: -1 (inválido) y 0 (válido, menor).

  • Frontera menor/adulto: 17 (menor) y 18 (adulto).

  • Frontera adulto/mayor: 64 (adulto) y 65 (mayor).

  • Límite superior de validez: 130 (mayor) y 131 (inválido).

1
2
3
4
5
6
7
8
9
10
11
void test_bordes_clasificar_edad(void)
{
    assert(clasificar_edad(-1) == -1);
    assert(clasificar_edad(0) == 1);
    assert(clasificar_edad(17) == 1);
    assert(clasificar_edad(18) == 2);
    assert(clasificar_edad(64) == 2);
    assert(clasificar_edad(65) == 3);
    assert(clasificar_edad(130) == 3);
    assert(clasificar_edad(131) == -1);
}

Ejercicio 4.7 - Comparación de punto flotante en testing ⭐⭐☆☆☆

Solution to Exercise 7

En aritmética binaria de punto flotante (IEEE 754), números como 0.1 o 1.0/3.0 no tienen representación exacta finita, lo que produce pequeñas discrepancias en los últimos decimales.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <assert.h>
#include <math.h>
#include <stdbool.h>

bool son_cercanos(double a, double b, double epsilon)
{
    return fabs(a - b) < epsilon;
}

void test_suma_decimales(void)
{
    double resultado = 0.1 + 0.2;
    double esperado = 0.3;
    assert(son_cercanos(resultado, esperado, 1e-9));
}

Ejercicio 4.8 - Aserciones defensivas vs Validación de Usuario ⭐☆☆☆☆

Solution to Exercise 8
  1. Validación de Usuario (if / manejo de errores): Es obligatoria para entradas externas incontrolables (teclado, archivos, red). El programa debe manejar el error con gracia, avisar al usuario y recuperarse.

  2. Aserciones (assert): Son herramientas de depuración para detectar bugs de lógica del programador (violaciones de contrato que “nunca deberían ocurrir” si el código estuviera bien escrito).


Ejercicio 4.9 - Función pura para cálculo de descuento ⭐⭐☆☆☆

Solution to Exercise 9
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <assert.h>
#include <stdbool.h>

double aplicar_descuento(double precio_base, double porcentaje)
{
    if (precio_base < 0.0 || porcentaje < 0.0 || porcentaje > 100.0)
    {
        return -1.0;
    }
    return precio_base * (1.0 - (porcentaje / 100.0));
}

void test_descuento_normal(void)
{
    assert(aplicar_descuento(200.0, 25.0) == 150.0);
}

void test_descuento_cero(void)
{
    assert(aplicar_descuento(100.0, 0.0) == 100.0);
}

void test_descuento_total(void)
{
    assert(aplicar_descuento(100.0, 100.0) == 0.0);
}

void test_descuento_precio_invalido(void)
{
    assert(aplicar_descuento(-50.0, 10.0) == -1.0);
}

void test_descuento_porcentaje_invalido(void)
{
    assert(aplicar_descuento(100.0, 150.0) == -1.0);
}

Ejercicio 4.10 - Estructura de Runner de Tests sin bibliotecas ⭐⭐☆☆☆

Solution to Exercise 10
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <assert.h>
#include <stdio.h>

int test_cuadrado_positivo(void)
{
    assert(2 * 2 == 4);
    return 1;
}

int test_cuadrado_cero(void)
{
    assert(0 * 0 == 0);
    return 1;
}

int main(void)
{
    int pasados = 0;
    int totales = 0;

    totales++;
    pasados += test_cuadrado_positivo();

    totales++;
    pasados += test_cuadrado_cero();

    printf("Resultados: %d/%d tests aprobados.\n", pasados, totales);
    return (pasados == totales) ? 0 : 1;
}

2. Variables Globales y Efectos Colaterales

Ejercicio 4.11 - El misterio del test que falla según el orden ⭐⭐⭐☆☆

Solution to Exercise 11

total_acumulado es una variable global mutable. Al terminar test_uno(), su valor queda en 15. Cuando inicia test_dos(), acumular(20) suma 20 a 15, resultando en 35 en lugar de 20, provocando la caída del assert.

Refactorización a función pura:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int acumular(int total_actual, int valor)
{
    return total_actual + valor;
}

void test_uno(void)
{
    int total = 0;
    total = acumular(total, 5);
    total = acumular(total, 10);
    assert(total == 15);
}

void test_dos(void)
{
    int total = 0;
    total = acumular(total, 20);
    assert(total == 20);
}

Ejercicio 4.12 - Sombreado de Variables (Shadowing) ⭐⭐☆☆☆

Solution to Exercise 12

Imprime: Resultado: 10, Factor: 10.

La variable local factor = 2 dentro de multiplicar oculta (“ensombrece”) a la variable global factor = 10 durante el alcance de la función. Al salir de la función, la global permanece inalterada con valor 10. La bandera -Wshadow alerta explícitamente sobre estas declaraciones ambiguas.


Ejercicio 4.13 - Refactorización de Cuenta Bancaria con Estado Global ⭐⭐⭐☆☆

Solution to Exercise 13
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <assert.h>
#include <stdbool.h>

double depositar(double saldo_actual, double monto)
{
    if (monto <= 0.0 || saldo_actual < 0.0)
    {
        return saldo_actual;
    }
    return saldo_actual + monto;
}

double extraer(double saldo_actual, double monto, bool *exito)
{
    if (monto <= 0.0 || monto > saldo_actual)
    {
        *exito = false;
        return saldo_actual;
    }
    *exito = true;
    return saldo_actual - monto;
}

void test_operaciones_bancarias_puras(void)
{
    double saldo = 1000.0;
    bool ok = false;

    saldo = depositar(saldo, 500.0);
    assert(saldo == 1500.0);

    saldo = extraer(saldo, 200.0, &ok);
    assert(ok == true);
    assert(saldo == 1300.0);

    saldo = extraer(saldo, 2000.0, &ok);
    assert(ok == false);
    assert(saldo == 1300.0);
}

Ejercicio 4.14 - Clasificación de funciones: Puras vs Impuras ⭐⭐☆☆☆

Solution to Exercise 14
  1. duplicar (Pura): Para el mismo valor de entrada siempre devuelve el mismo resultado y no produce ningún efecto secundario.

  2. obtener_siguiente (Impura): Modifica y depende de la variable global g_contador. Llamadas sucesivas devuelven valores distintos.

  3. sumar_con_log (Impura): Produce un efecto secundario en la consola mediante printf.

  4. es_positivo (Pura): Cálculo determinístico basado únicamente en su parámetro.


Ejercicio 4.15 - Variable static local vs Variable Global ⭐⭐⭐☆☆

Solution to Exercise 15

La primera llamada retorna 1, la segunda 2, la tercera 3, etc.

Aunque su alcance está restringido a la función, su tiempo de vida es durante toda la ejecución del programa. Esto retiene estado mutable entre pruebas, imposibilitando correr tests unitarios aislados y repetibles sin reiniciar el binario.


Ejercicio 4.16 - Modificación inadvertida de bandera global ⭐⭐☆☆☆

Solution to Exercise 16

validar_rango(-5) detecta el error y pone hay_error = true. Pero luego validar_paridad(4) como 4 es par, ejecuta la rama else y pone hay_error = false, borrando el error detectado previamente.

Refactorización:

1
2
3
4
5
6
7
8
9
bool es_rango_valido(int x)
{
    return (x >= 0 && x <= 100);
}

bool es_par(int x)
{
    return (x % 2 == 0);
}

Ejercicio 4.17 - Acoplamiento espurio entre módulos ⭐⭐⭐☆☆

Solution to Exercise 17

Cuando dos módulos acceden a una variable global compartida mediante extern:

  1. No existe un contrato explícito sobre quién tiene derecho a modificarla ni cuándo.

  2. Es imposible razonar sobre el estado de un módulo sin inspeccionar todo el código fuente del otro.

  3. Las pruebas unitarias de un módulo requieren simular o sincronizar el estado global del otro, eliminando el aislamiento.


Ejercicio 4.18 - Conversor de Temperaturas desacoplado de estado ⭐⭐☆☆☆

Solution to Exercise 18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <assert.h>
#include <math.h>
#include <stdbool.h>

bool son_cercanos(double a, double b, double eps)
{
    return fabs(a - b) < eps;
}

double celsius_a_fahrenheit(double c)
{
    return (c * 9.0 / 5.0) + 32.0;
}

double fahrenheit_a_celsius(double f)
{
    return (f - 32.0) * 5.0 / 9.0;
}

double celsius_a_kelvin(double c)
{
    return c + 273.15;
}

void test_celsius_a_fahrenheit_cero(void)
{
    assert(son_cercanos(celsius_a_fahrenheit(0.0), 32.0, 1e-6));
}

void test_celsius_a_fahrenheit_ebullicion(void)
{
    assert(son_cercanos(celsius_a_fahrenheit(100.0), 212.0, 1e-6));
}

void test_celsius_a_kelvin_cero(void)
{
    assert(son_cercanos(celsius_a_kelvin(0.0), 273.15, 1e-6));
}

Ejercicio 4.19 - Simulación de cronómetro sin estado global ⭐⭐⭐☆☆

Solution to Exercise 19
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <assert.h>

int tiempo_avanzar(int tiempo_actual, int segundos_delta)
{
    if (segundos_delta < 0)
    {
        return tiempo_actual;
    }
    return tiempo_actual + segundos_delta;
}

int tiempo_reiniciar(void)
{
    return 0;
}

int tiempo_obtener_minutos(int tiempo_total)
{
    return tiempo_total / 60;
}

int tiempo_obtener_segundos(int tiempo_total)
{
    return tiempo_total % 60;
}

void test_cronometro_avance(void)
{
    int t = tiempo_reiniciar();
    t = tiempo_avanzar(t, 45);
    t = tiempo_avanzar(t, 30);
    assert(t == 75);
    assert(tiempo_obtener_minutos(t) == 1);
    assert(tiempo_obtener_segundos(t) == 15);
}

Ejercicio 4.20 - Detección de fugas de estado entre funciones ⭐⭐☆☆☆

Solution to Exercise 20

Si una parte del programa llama a multiplicar_y_guardar(3, 4), el resultado esperado es 12. Pero si otra parte del programa llama a multiplicar_y_guardar(10, 2) antes de que la primera lea ultimo_resultado, el valor se sobrescribe a 20, corrompiendo los datos del primer llamador.


Ejercicio 4.21 - Calculadora con memoria local vs global ⭐⭐⭐☆☆

Solution to Exercise 21
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <assert.h>
#include <stdbool.h>

double calc_sumar(double acumulador, double valor)
{
    return acumulador + valor;
}

double calc_restar(double acumulador, double valor)
{
    return acumulador - valor;
}

double calc_multiplicar(double acumulador, double valor)
{
    return acumulador * valor;
}

double calc_dividir(double acumulador, double valor, bool *ok)
{
    if (valor == 0.0)
    {
        *ok = false;
        return acumulador;
    }
    *ok = true;
    return acumulador / valor;
}

void test_secuencia_calculadora(void)
{
    double acc = 0.0;
    bool ok = false;

    acc = calc_sumar(acc, 50.0);
    acc = calc_multiplicar(acc, 2.0);
    acc = calc_restar(acc, 20.0);
    acc = calc_dividir(acc, 4.0, &ok);

    assert(ok == true);
    assert(acc == 20.0);
}

Ejercicio 4.22 - Máquina de Estados Finita Pura ⭐⭐⭐☆☆

Solution to Exercise 22
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <assert.h>
#include <ctype.h>

int transicion_estado(int estado_actual, char entrada)
{
    if (estado_actual == 0)
    {
        return isalpha(entrada) ? 1 : -1;
    }
    if (estado_actual == 1)
    {
        return isdigit(entrada) ? 2 : 1;
    }
    if (estado_actual == 2)
    {
        return 2;
    }
    return -1;
}

void test_fsm_camino_exitoso(void)
{
    int estado = 0;
    estado = transicion_estado(estado, a);
    assert(estado == 1);
    estado = transicion_estado(estado, b);
    assert(estado == 1);
    estado = transicion_estado(estado, '9\);
    assert(estado == 2);
}

3. Lectura, Análisis y Flujo de Control

Ejercicio 4.23 - Cobertura de Ramas (Branch Coverage) ⭐⭐☆☆☆

Solution to Exercise 4

Se requieren al menos 5 casos de prueba para cubrir todas las ramas:

  1. asistencia_pct < 75 (ej. 50, 80, 80) \rightarrow 0

  2. parcial1 >= 60 && parcial2 >= 60 (ej. 80, 70, 80) \rightarrow 2

  3. parcial1 >= 60 pero parcial2 < 60 y >= 40 (ej. 70, 50, 80) \rightarrow 1

  4. parcial1 < 60 pero parcial2 >= 40 (ej. 30, 50, 80) \rightarrow 1

  5. Ambos parciales < 40 con asistencia (ej. 30, 20, 80) \rightarrow 0


Ejercicio 4.24 - Detección de Código Muerto (Dead Code) ⭐⭐☆☆☆

Solution to Exercise 24

La línea return 0; al final es código muerto porque los tres bloques if anteriores cubren exhaustivamente todos los números enteros posibles (extedad<0 ext{edad} < 0, 0extedad120 \le ext{edad} \le 12, extedad>12 ext{edad} > 12).

Versión refactorizada y limpia:

1
2
3
4
5
6
7
8
9
10
11
12
int calcular_tarifa(int edad)
{
    if (edad < 0)
    {
        return -1;
    }
    if (edad <= 12)
    {
        return 50;
    }
    return 100;
}

Ejercicio 4.25 - Cortocircuito Lógico y Orden de Evaluación ⭐⭐☆☆☆

Solution to Exercise 25

Imprime:

Llamadas tras primer if: 0
Rama 2
Llamadas tras segundo if: 0
  • En a != 0 && incrementar(), como a != 0 es falso, el operador && evalúa a falso sin evaluar la segunda parte.

  • En b == 1 || incrementar(), como b == 1 es verdadero, el operador || evalúa a verdadero sin evaluar la segunda parte. En ningún caso se ejecuta incrementar().


Ejercicio 4.26 - Cláusulas de Guarda vs Código en Flecha ⭐⭐☆☆☆

Solution to Exercise 26
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <assert.h>
#include <stdbool.h>

int procesar_pedido(int cantidad, double precio_unitario, bool cliente_vip)
{
    if (cantidad <= 0 || precio_unitario <= 0.0)
    {
        return -1;
    }
    if (cliente_vip)
    {
        return (int)(cantidad * precio_unitario * 0.85);
    }
    return (int)(cantidad * precio_unitario);
}

void test_pedido_invalido_cantidad(void)
{
    assert(procesar_pedido(0, 10.0, false) == -1);
}

void test_pedido_invalido_precio(void)
{
    assert(procesar_pedido(5, -2.0, false) == -1);
}

void test_pedido_regular(void)
{
    assert(procesar_pedido(10, 10.0, false) == 100);
}

void test_pedido_vip(void)
{
    assert(procesar_pedido(10, 10.0, true) == 85);
}

Ejercicio 4.27 - Detección de Fallthrough accidental en switch ⭐⭐☆☆☆

Solution to Exercise 27

En el código original falta la instrucción break; al final de cada grupo de casos. Por lo tanto, para cualquier mes, la ejecución continúa cayendo (fallthrough) hasta el bloque default, retornando siempre -1.

Solución:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int dias_del_mes(int mes)
{
    switch (mes)
    {
        case 2:
            return 28;
        case 4:
        case 6:
        case 9:
        case 11:
            return 30;
        case 1:
        case 3:
        case 5:
        case 7:
        case 8:
        case 10:
        case 12:
            return 31;
        default:
            return -1;
    }
}

Ejercicio 4.28 - Detección de Error Off-by-one en Lazos ⭐⭐☆☆☆

Solution to Exercise 28

El lazo utiliza i < n, lo que omite sumar el último valor NN. Para N=4N=4, suma 1+2+3=61+2+3 = 6 en lugar de 1+2+3+4=101+2+3+4 = 10.

Corrección:

1
2
3
4
5
6
7
8
9
10
11
12
13
int suma_primeros_n(int n)
{
    if (n <= 0)
    {
        return 0;
    }
    int total = 0;
    for (int i = 1; i <= n; i++)
    {
        total += i;
    }
    return total;
}

Ejercicio 4.29 - Desacople de Entrada/Salida para Testabilidad ⭐⭐⭐☆☆

Solution to Exercise 29
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <assert.h>
#include <stdbool.h>

float calcular_imc(float peso, float altura)
{
    if (peso <= 0.0f || altura <= 0.0f)
    {
        return -1.0f;
    }
    return peso / (altura * altura);
}

int clasificar_imc(float imc)
{
    if (imc <= 0.0f)
    {
        return -1; // Inválido
    }
    if (imc < 18.5f)
    {
        return 1; // Bajo peso
    }
    if (imc < 25.0f)
    {
        return 2; // Normal
    }
    return 3; // Sobrepeso
}

void test_calculo_y_clasificacion_imc(void)
{
    float imc = calcular_imc(70.0f, 1.75f);
    assert(imc > 22.8f && imc < 22.9f);
    assert(clasificar_imc(imc) == 2);
    assert(clasificar_imc(16.0f) == 1);
    assert(clasificar_imc(28.0f) == 3);
    assert(clasificar_imc(-1.0f) == -1);
}

Ejercicio 4.30 - Tabla de Traza de Ejecución (Trace Table) ⭐⭐☆☆☆

Solution to Exercise 30
Iteracióna (inicio)b (inicio)temp (b)a % bb (nuevo)a (nuevo)Condición (b != 0)
Inicio1812----true
11812126612true
21266006false
Fin60----Retorna 6

Ejercicio 4.31 - Principio de Responsabilidad Única (SRP) ⭐⭐☆☆☆

Solution to Exercise 31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <assert.h>

int calcular_suma(const int arr[], int n)
{
    int total = 0;
    for (int i = 0; i < n; i++)
    {
        total += arr[i];
    }
    return total;
}

int encontrar_maximo(const int arr[], int n)
{
    if (n <= 0)
    {
        return 0;
    }
    int max = arr[0];
    for (int i = 1; i < n; i++)
    {
        if (arr[i] > max)
        {
            max = arr[i];
        }
    }
    return max;
}

int contar_negativos(const int arr[], int n)
{
    int cont = 0;
    for (int i = 0; i < n; i++)
    {
        if (arr[i] < 0)
        {
            cont++;
        }
    }
    return cont;
}

void test_funciones_atomicas(void)
{
    int datos[] = {3, -1, 8, -5, 2};
    assert(calcular_suma(datos, 5) == 7);
    assert(encontrar_maximo(datos, 5) == 8);
    assert(contar_negativos(datos, 5) == 2);
}

Ejercicio 4.32 - Testeo de Año Bisiesto Exhaustivo ⭐⭐☆☆☆

Solution to Exercise 32
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <assert.h>
#include <stdbool.h>

bool es_bisiesto(int anio)
{
    if (anio <= 0)
    {
        return false;
    }
    return ((anio % 4 == 0 && anio % 100 != 0) || (anio % 400 == 0));
}

void test_bisiesto_multiplo_4(void)
{
    assert(es_bisiesto(2024) == true);
}

void test_no_bisiesto_comun(void)
{
    assert(es_bisiesto(2023) == false);
}

void test_no_bisiesto_fin_de_siglo(void)
{
    assert(es_bisiesto(1900) == false);
}

void test_bisiesto_cuatrisecular(void)
{
    assert(es_bisiesto(2000) == true);
}

void test_bisiesto_invalido(void)
{
    assert(es_bisiesto(-400) == false);
}

Ejercicio 4.33 - Detección de mutación en arreglos de solo lectura ⭐⭐☆☆☆

Solution to Exercise 33

El calificador const garantiza contractualmente que la función es de solo lectura. Si por error un programador escribe arr[i] = 0; en lugar de if (arr[i] == 0), el compilador rechaza la compilación con un error inmediato (assignment of read-only location), evitando corromper los datos del llamador.


Ejercicio 4.34 - Búsqueda de errores lógicos con aserciones de invariante ⭐⭐⭐☆☆

Solution to Exercise 34
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <assert.h>

void test_division_con_invariante(void)
{
    int c = 0;
    int r = 0;

    division_lenta(10, 3, &c, &r);
    assert(c == 3 && r == 1);

    division_lenta(20, 5, &c, &r);
    assert(c == 4 && r == 0);

    division_lenta(3, 7, &c, &r);
    assert(c == 0 && r == 3);
}

4. Estilo, Estructura y Contratos

Ejercicio 4.35 - Verificación de Estilo Allman ⭐☆☆☆☆

Solution to Exercise 35
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <assert.h>

int valor_absoluto(int n)
{
    if (n < 0)
    {
        return -n;
    }
    else
    {
        return n;
    }
}

void test_valor_absoluto(void)
{
    assert(valor_absoluto(-12) == 12);
    assert(valor_absoluto(0) == 0);
    assert(valor_absoluto(15) == 15);
}

Ejercicio 4.36 - Eliminación de Números Mágicos ⭐⭐☆☆☆

Solution to Exercise 36
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <assert.h>

#define PESO_LIMITE_LIVIANO 5.0
#define PESO_LIMITE_MEDIO 20.0
#define TARIFA_BASE_LIVIANO 500.0
#define TARIFA_BASE_MEDIO 1700.0
#define COSTO_KG_MEDIO 80.0
#define COSTO_KG_PESADO 120.0

double calcular_costo_envio(double peso)
{
    if (peso <= 0.0)
    {
        return -1.0;
    }
    if (peso <= PESO_LIMITE_LIVIANO)
    {
        return TARIFA_BASE_LIVIANO;
    }
    if (peso <= PESO_LIMITE_MEDIO)
    {
        return TARIFA_BASE_LIVIANO + (peso - PESO_LIMITE_LIVIANO) * COSTO_KG_MEDIO;
    }
    return TARIFA_BASE_MEDIO + (peso - PESO_LIMITE_MEDIO) * COSTO_KG_PESADO;
}

void test_costo_envio_limites(void)
{
    assert(calcular_costo_envio(5.0) == 500.0);
    assert(calcular_costo_envio(10.0) == 900.0);
    assert(calcular_costo_envio(20.0) == 1700.0);
}

Ejercicio 4.37 - Documentación de Contratos con Doxygen ⭐⭐☆☆☆

Solution to Exercise 37
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <assert.h>
#include <stdbool.h>

/**
 * @brief Calcula la potencia entera de una base elevada a un exponente no negativo.
 *
 * @param base Base entera.
 * @param exponente Exponente entero.
 * @param ok Puntero a booleano de estado de éxito.
 *
 * @pre `exponente >= 0`
 * @pre `ok != NULL`
 *
 * @returns El resultado de base^exponente si ok es true, o 0 en caso de error.
 *
 * @post Si exponente == 0, el retorno es 1 y *ok == true.
 */
int calcular_potencia_entera(int base, int exponente, bool *ok)
{
    if (ok == NULL || exponente < 0)
    {
        if (ok != NULL)
        {
            *ok = false;
        }
        return 0;
    }
    *ok = true;
    int resultado = 1;
    for (int i = 0; i < exponente; i++)
    {
        resultado *= base;
    }
    return resultado;
}

void test_potencia_contrato(void)
{
    bool ok = false;
    assert(calcular_potencia_entera(2, 3, &ok) == 8 && ok);
    assert(calcular_potencia_entera(5, 0, &ok) == 1 && ok);
    calcular_potencia_entera(2, -1, &ok);
    assert(ok == false);
}

Ejercicio 4.38 - Inicialización obligatoria de variables ⭐☆☆☆☆

Solution to Exercise 38

Al no inicializarse, contador contiene un valor residual indeterminado proveniente de la memoria de la pila. El incremento contador++ genera comportamiento indefinido y resultados erróneos impredecibles.

Corrección:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int contar_multiplos(const int arr[], int n, int divisor)
{
    if (divisor == 0)
    {
        return -1;
    }
    int contador = 0;
    for (int i = 0; i < n; i++)
    {
        if (arr[i] % divisor == 0)
        {
            contador++;
        }
    }
    return contador;
}

Ejercicio 4.39 - Organización de Archivo .c Estándar ⭐☆☆☆☆

Solution to Exercise 39

El orden estándar según la Regla 0x5005h es:

  1. Inclusiones de bibliotecas estándar (#include <stdio.h>).

  2. Inclusiones de cabeceras del proyecto (#include "modulo.h").

  3. Definición de macros y constantes (#define).

  4. Prototipos de funciones privadas (static int auxiliar(void);).

  5. Función main (si es el archivo principal) o funciones públicas.

  6. Implementación de funciones privadas (static).


Ejercicio 4.40 - Red de Seguridad: Default en Switch ⭐⭐☆☆☆

Solution to Exercise 40

El bloque default actúa como una red de seguridad defensiva ante datos imprevistos, corrupción de memoria o adición futura de nuevos valores en el dominio que no fueron contemplados.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <assert.h>

int obtener_dias_trimestre(int trimestre)
{
    switch (trimestre)
    {
        case 1:
            return 90;
        case 2:
            return 91;
        case 3:
            return 92;
        case 4:
            return 92;
        default:
            return -1; // Captura de valores inválidos
    }
}

void test_trimestres(void)
{
    assert(obtener_dias_trimestre(1) == 90);
    assert(obtener_dias_trimestre(5) == -1);
    assert(obtener_dias_trimestre(-1) == -1);
}

Ejercicio 4.41 - Nombres de Variables Autoexplicativos ⭐☆☆☆☆

Solution to Exercise 41
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <assert.h>

double calcular_precio_final(double precio_base, double porcentaje_descuento, double porcentaje_impuesto)
{
    double precio_con_descuento = precio_base - (precio_base * (porcentaje_descuento / 100.0));
    double precio_final = precio_con_descuento + (precio_con_descuento * (porcentaje_impuesto / 100.0));
    return precio_final;
}

void test_precio_final(void)
{
    assert(calcular_precio_final(100.0, 10.0, 21.0) == 108.9);
}

Ejercicio 4.42 - Prohibición de Reutilización de Variables ⭐⭐☆☆☆

Solution to Exercise 42

Reutilizar la misma variable para propósitos disímiles dificulta la lectura, impide agregar aserciones claras sobre estados intermedios y propaga errores si un bloque asume que la variable conserva su valor anterior.


Ejercicio 4.43 - Flags de Compilación Obligatorios ⭐☆☆☆☆

Solution to Exercise 43
  • -Wall: Habilita todas las advertencias comunes sobre construcciones dudosas (variables no usadas, conversiones implícitas riesgosas).

  • -Wextra: Habilita advertencias adicionales más rigurosas (comparaciones con signo/sin signo, parámetros no utilizados).

  • -Werror: Trata todas las advertencias como errores de compilación, impidiendo generar el binario si existen problemas.

  • -Wpedantic: Exige estricto apego al estándar ISO de C, rechazando extensiones propietarias del compilador.


Ejercicio 4.44 - Funciones Auxiliares Privadas static ⭐⭐☆☆☆

Solution to Exercise 44

El calificador static en una función a nivel de archivo restringe su visibilidad exclusivamente a la unidad de traducción actual (internal linkage). Esto encapsula los detalles de implementación internos y previene errores de símbolos duplicados en tiempo de enlace (linker collision).


5. Suites de Pruebas Integradoras y Diagnóstico

Ejercicio 4.45 - Suite de Pruebas: Validador de Fecha ⭐⭐⭐☆☆

Solution to Exercise 45
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#include <assert.h>
#include <stdbool.h>

bool es_bisiesto_aux(int anio)
{
    return (anio % 4 == 0 && anio % 100 != 0) || (anio % 400 == 0);
}

bool es_fecha_valida(int dia, int mes, int anio)
{
    if (anio <= 0 || mes < 1 || mes > 12 || dia < 1)
    {
        return false;
    }
    int dias_max = 31;
    if (mes == 4 || mes == 6 || mes == 9 || mes == 11)
    {
        dias_max = 30;
    }
    else if (mes == 2)
    {
        dias_max = es_bisiesto_aux(anio) ? 29 : 28;
    }
    return dia <= dias_max;
}

void test_fecha_31_dias(void)
{
    assert(es_fecha_valida(31, 1, 2024) == true);
    assert(es_fecha_valida(32, 1, 2024) == false);
}

void test_fecha_30_dias(void)
{
    assert(es_fecha_valida(30, 4, 2024) == true);
    assert(es_fecha_valida(31, 4, 2024) == false);
}

void test_febrero_bisiesto(void)
{
    assert(es_fecha_valida(29, 2, 2024) == true);
    assert(es_fecha_valida(29, 2, 2023) == false);
}

void test_fecha_mes_invalido(void)
{
    assert(es_fecha_valida(15, 13, 2024) == false);
    assert(es_fecha_valida(15, 0, 2024) == false);
}

void correr_pruebas_fechas(void)
{
    test_fecha_31_dias();
    test_fecha_30_dias();
    test_febrero_bisiesto();
    test_fecha_mes_invalido();
}

Ejercicio 4.46 - Suite de Pruebas: Tarifa de Estacionamiento ⭐⭐⭐☆☆

Solution to Exercise 46
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <assert.h>
#include <math.h>
#include <stdbool.h>

double calcular_estacionamiento(int minutos)
{
    if (minutos <= 0)
    {
        return 0.0;
    }
    int horas = (minutos + 59) / 60; // Redondeo hacia arriba a la hora siguiente
    double total = 200.0;
    if (horas > 1)
    {
        total += (horas - 1) * 150.0;
    }
    if (horas > 6)
    {
        total *= 0.90;
    }
    if (total > 1200.0)
    {
        total = 1200.0;
    }
    return total;
}

void test_estacionamiento_primera_hora(void)
{
    assert(calcular_estacionamiento(30) == 200.0);
    assert(calcular_estacionamiento(60) == 200.0);
}

void test_estacionamiento_horas_adicionales(void)
{
    assert(calcular_estacionamiento(61) == 350.0); // 2 horas
    assert(calcular_estacionamiento(180) == 500.0); // 3 horas
}

void test_estacionamiento_descuento_larga_estadia(void)
{
    // 7 horas = 200 + 6*150 = 1100. Con 10% desc = 990.
    assert(calcular_estacionamiento(420) == 990.0);
}

void test_estacionamiento_tope_maximo(void)
{
    assert(calcular_estacionamiento(1440) == 1200.0);
}

Ejercicio 4.47 - Diagnóstico de Aserción Fallida ⭐⭐☆☆☆

Solution to Exercise 47

Si se pasa a = 0 y b = 5, la primera llamada no entra en b == 0. Llama a mcd(5, 0 % 5) que es mcd(5, 0), el cual entra en b == 0 y retorna 5.

Si la función original no contempla a < 0 o b < 0, los módulos con signo en C pueden generar números negativos. La corrección robusta es:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <assert.h>
#include <stdlib.h>

int mcd(int a, int b)
{
    a = abs(a);
    b = abs(b);
    while (b != 0)
    {
        int temp = b;
        b = a % b;
        a = temp;
    }
    return a;
}

Ejercicio 4.48 - Test de Regresión ⭐⭐☆☆☆

Solution to Exercise 48
  1. Procedimiento:

    • Reproducir el bug reportado.

    • Escribir una prueba unitaria específica que falle debido a ese bug.

    • Modificar el código fuente hasta que la prueba pase.

    • Incorporar la nueva prueba a la suite continua del proyecto.

  2. Escribir la prueba primero confirma científicamente la existencia del bug y asegura que la corrección realmente solucione la causa raíz sin depender de verificaciones manuales.


Ejercicio 4.49 - Suite de Pruebas: Desglose de Billetes ⭐⭐⭐☆☆

Solution to Exercise 49
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <assert.h>
#include <stdbool.h>

bool desglosar_monto(int monto, int *b1000, int *b500, int *b200, int *b100)
{
    if (monto <= 0 || monto % 100 != 0 || !b1000 || !b500 || !b200 || !b100)
    {
        return false;
    }
    *b1000 = monto / 1000;
    monto %= 1000;

    *b500 = monto / 500;
    monto %= 500;

    *b200 = monto / 200;
    monto %= 200;

    *b100 = monto / 100;
    return true;
}

void test_desglose_combinado(void)
{
    int mil = 0, quin = 0, dosc = 0, cien = 0;
    bool ok = desglosar_monto(3800, &mil, &quin, &dosc, &cien);
    assert(ok == true);
    assert(mil == 3 && quin == 1 && dosc == 1 && cien == 1);
}

void test_desglose_monto_invalido(void)
{
    int mil = 0, quin = 0, dosc = 0, cien = 0;
    assert(desglosar_monto(350, &mil, &quin, &dosc, &cien) == false);
    assert(desglosar_monto(-100, &mil, &quin, &dosc, &cien) == false);
}

Ejercicio 4.50 - Suite de Pruebas: Validador de Números Primos ⭐⭐⭐☆☆

Solution to Exercise 50
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <assert.h>
#include <stdbool.h>

bool es_primo(int n)
{
    if (n <= 1)
    {
        return false;
    }
    if (n == 2)
    {
        return true;
    }
    if (n % 2 == 0)
    {
        return false;
    }
    for (int i = 3; i * i <= n; i += 2)
    {
        if (n % i == 0)
        {
            return false;
        }
    }
    return true;
}

void test_primos_menores_o_iguales_uno(void)
{
    assert(es_primo(0) == false);
    assert(es_primo(1) == false);
    assert(es_primo(-7) == false);
}

void test_primos_basicos(void)
{
    assert(es_primo(2) == true);
    assert(es_primo(3) == true);
    assert(es_primo(13) == true);
    assert(es_primo(997) == true);
}

void test_compuestos_impares(void)
{
    assert(es_primo(9) == false);
    assert(es_primo(25) == false);
    assert(es_primo(49) == false);
}

Ejercicio 4.51 - Testeo de Funciones con Códigos de Error ⭐⭐⭐☆☆

Solution to Exercise 51
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <assert.h>
#include <stddef.h>

int calcular_velocidad(double distancia, double tiempo, double *velocidad)
{
    if (velocidad == NULL)
    {
        return 3;
    }
    if (distancia < 0.0)
    {
        return 1;
    }
    if (tiempo <= 0.0)
    {
        return 2;
    }
    *velocidad = distancia / tiempo;
    return 0;
}

void test_velocidad_exito(void)
{
    double v = 0.0;
    int codigo = calcular_velocidad(100.0, 2.0, &v);
    assert(codigo == 0);
    assert(v == 50.0);
}

void test_velocidad_distancia_invalida(void)
{
    double v = -99.0;
    int codigo = calcular_velocidad(-10.0, 2.0, &v);
    assert(codigo == 1);
    assert(v == -99.0);
}

void test_velocidad_tiempo_invalido(void)
{
    double v = -99.0;
    int codigo = calcular_velocidad(100.0, 0.0, &v);
    assert(codigo == 2);
    assert(v == -99.0);
}

void test_velocidad_puntero_nulo(void)
{
    int codigo = calcular_velocidad(100.0, 2.0, NULL);
    assert(codigo == 3);
}

Ejercicio 4.52 - Suite de Pruebas: Palíndromo Numérico ⭐⭐☆☆☆

Solution to Exercise 52
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <assert.h>
#include <stdbool.h>

int invertir_entero(int n)
{
    int invertido = 0;
    while (n > 0)
    {
        invertido = (invertido * 10) + (n % 10);
        n /= 10;
    }
    return invertido;
}

bool es_numero_capicua(int n)
{
    if (n < 0)
    {
        return false;
    }
    if (n < 10)
    {
        return true;
    }
    return n == invertir_entero(n);
}

void test_capicua_un_digito(void)
{
    assert(es_numero_capicua(0) == true);
    assert(es_numero_capicua(7) == true);
}

void test_capicua_multiples_digitos(void)
{
    assert(es_numero_capicua(1221) == true);
    assert(es_numero_capicua(12321) == true);
    assert(es_numero_capicua(123) == false);
    assert(es_numero_capicua(-121) == false);
}

Ejercicio 4.53 - Suite de Pruebas: Validador de Triángulo ⭐⭐⭐☆☆

Solution to Exercise 53
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <assert.h>
#include <math.h>
#include <stdbool.h>

bool lados_cercanos(double x, double y)
{
    return fabs(x - y) < 1e-6;
}

int clasificar_triangulo(double a, double b, double c)
{
    if (a <= 0.0 || b <= 0.0 || c <= 0.0)
    {
        return 0;
    }
    if (a + b <= c || a + c <= b || b + c <= a)
    {
        return 0; // Desigualdad triangular violada
    }
    if (lados_cercanos(a, b) && lados_cercanos(b, c))
    {
        return 1; // Equilátero
    }
    if (lados_cercanos(a, b) || lados_cercanos(a, c) || lados_cercanos(b, c))
    {
        return 2; // Isósceles
    }
    return 3; // Escaleno
}

void test_triangulo_invalido(void)
{
    assert(clasificar_triangulo(1.0, 2.0, 3.0) == 0); // Degenerado
    assert(clasificar_triangulo(-1.0, 2.0, 2.0) == 0);
}

void test_triangulo_tipos(void)
{
    assert(clasificar_triangulo(5.0, 5.0, 5.0) == 1);
    assert(clasificar_triangulo(5.0, 5.0, 8.0) == 2);
    assert(clasificar_triangulo(3.0, 4.0, 5.0) == 3);
}

Ejercicio 4.54 - Suite de Pruebas: Conversión de Segundos a Tiempo ⭐⭐☆☆☆

Solution to Exercise 54
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <assert.h>
#include <stdbool.h>
#include <stddef.h>

bool segundos_a_tiempo(int total_segundos, int *horas, int *minutos, int *segundos)
{
    if (total_segundos < 0 || !horas || !minutos || !segundos)
    {
        return false;
    }
    *horas = total_segundos / 3600;
    int resto = total_segundos % 3600;
    *minutos = resto / 60;
    *segundos = resto % 60;
    return true;
}

void test_segundos_a_tiempo_normal(void)
{
    int h = 0, m = 0, s = 0;
    bool ok = segundos_a_tiempo(3661, &h, &m, &s);
    assert(ok == true);
    assert(h == 1 && m == 1 && s == 1);
}

void test_segundos_a_tiempo_cero(void)
{
    int h = 0, m = 0, s = 0;
    bool ok = segundos_a_tiempo(0, &h, &m, &s);
    assert(ok == true);
    assert(h == 0 && m == 0 && s == 0);
}

void test_segundos_a_tiempo_invalido(void)
{
    int h = 0, m = 0, s = 0;
    assert(segundos_a_tiempo(-10, &h, &m, &s) == false);
    assert(segundos_a_tiempo(100, NULL, &m, &s) == false);
}

Ejercicio 4.55 - Suite Integradora de Evaluación de Sistema de Calificaciones ⭐⭐⭐☆☆

Solution to Exercise 55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
#include <assert.h>
#include <stdbool.h>
#include <stdio.h>

bool validar_nota(int nota)
{
    return (nota >= 1 && nota <= 10);
}

double calcular_promedio_curso(const int notas[], int cantidad)
{
    if (notas == NULL || cantidad <= 0)
    {
        return -1.0;
    }
    int suma = 0;
    for (int i = 0; i < cantidad; i++)
    {
        if (!validar_nota(notas[i]))
        {
            return -1.0;
        }
        suma += notas[i];
    }
    return (double)suma / cantidad;
}

int contar_aprobados(const int notas[], int cantidad, int nota_minima)
{
    if (notas == NULL || cantidad <= 0 || !validar_nota(nota_minima))
    {
        return -1;
    }
    int aprobados = 0;
    for (int i = 0; i < cantidad; i++)
    {
        if (notas[i] >= nota_minima)
        {
            aprobados++;
        }
    }
    return aprobados;
}

int estado_final_alumno(int p1, int p2, int rec)
{
    if (!validar_nota(p1) || !validar_nota(p2))
    {
        return -1;
    }
    if (p1 >= 4 && p2 >= 4)
    {
        return (p1 + p2) / 2;
    }
    if (!validar_nota(rec))
    {
        return 0; // Reprobado sin recuperatorio válido
    }
    if (p1 < 4 && rec >= 4 && p2 >= 4)
    {
        return (rec + p2) / 2;
    }
    if (p2 < 4 && rec >= 4 && p1 >= 4)
    {
        return (p1 + rec) / 2;
    }
    return 0;
}

void test_sistema_calificaciones(void)
{
    int notas[] = {7, 8, 4, 10, 6};
    assert(calcular_promedio_curso(notas, 5) == 7.0);
    assert(contar_aprobados(notas, 5, 6) == 4);

    // Caso directo aprobado
    assert(estado_final_alumno(8, 6, 0) == 7);

    // Caso recupera primer parcial
    assert(estado_final_alumno(2, 8, 6) == 7);

    // Caso recupera segundo parcial
    assert(estado_final_alumno(7, 3, 5) == 6);

    // Caso reprueba recuperatorio
    assert(estado_final_alumno(2, 8, 3) == 0);

    // Caso notas inválidas
    assert(estado_final_alumno(12, 8, 5) == -1);
}

int main(void)
{
    test_sistema_calificaciones();
    printf("¡Suite integradora ejecutada con 100%% de éxito!\n");
    return 0;
}

Notas Finales