Skip to content

5. Resolución de los tres problemas con ChatGPT

5.1. Introducción

He aquí una primera captura de pantalla de una sesión de ChatGPT:

 
  • En [1-3], los tres problemas planteados a ChatGPT;
  • En [4], la URL de ChatGPT;
  • En [5], la versión de ChatGPT utilizada;

ChatGPT es un producto de OpenAI disponible en la URL [https://chatgpt.com/]. Para disponer de un historial de tus sesiones de preguntas y respuestas como el anterior, debes crear una cuenta. Por otra parte, al igual que todas las demás IA probadas, ChatGPT limita el número de preguntas y el número de archivos descargados. Cuando se alcanza este límite, la sesión finaliza y se te propone continuarla más tarde. Los límites impuestos por ChatGPT se alcanzan muy rápidamente. Para realizar este tutorial, tuve que contratar una suscripción de pago de un mes.

La interfaz de ChatGPT es la siguiente:

 
  • En [1], para adjuntar archivos a la pregunta formulada;
  • En [2], la pregunta formulada;
  • En [3], para iniciar la ejecución de la IA; ## 5.2. El problema 1

La pregunta a ChatGPT:

  
 

ChatGPT responde correctamente.

5.3. El problema 2

Se trata del cálculo del impuesto con el PDF. Para ser sinceros, vamos a utilizar el PDF generado por Gemini, que corrige los errores del PDF inicial.

 
  • En [1], se proporcionó el PDF generado por Gemini;
  • En [2], hemos añadido la prueba unitaria con la que Gemini demostró su superioridad:
test12: (2, 2, 49500) -> (1297, 431, 324)

Lanzamos ChatGPT. Tarda unos 3 minutos en generar su respuesta. A diferencia de Gemini, sí que proporciona un enlace que funciona para recuperar el script generado. Cargamos este en PyCharm:

 

El script [chatGPT1] funciona a la primera. Aquí no hay color: en este problema, ChatGPT ha tenido un mejor rendimiento que Gemini.

El script [chatGPT1] proporcionado por ChatGPT es el siguiente:

# -*- coding: utf-8 -*-
"""
Cálculo del impuesto de 2019 (ingresos de 2018) en Python.

Supuestos y reglas (según el PDF proporcionado):
- Todos los ingresos se atribuyen al declarante 1 (incluso en el caso de una pareja).
- Desgravación fija del 10 % con un mínimo y un máximo.
- Escala progresiva de 2018 aplicada al coeficiente familiar (QF).
- Límite máximo del coeficiente familiar: ventaja por media parte por hijo limitada.
- Descuento y reducción del 20 % para rentas bajas, con la regla de repesca.
- Orden de las correcciones: límite máximo del QF -> descuento -> reducción del 20 %.
- Redondeos: deducción (↑), descuento (↑), reducción (↑), impuesto final (↓).
- Se redondean el impuesto a pagar (redondeado al euro inferior), la deducción y la reducción
  (redondeados al euro superior).

Los números «mágicos» se agrupan en constantes, y cada paso se aísla
en funciones para mayor claridad.
"""

from dataclasses import dataclass
from math import ceil, floor

# ---------- Constantes reglamentarias ----------

# Descuento por gastos profesionales (mínimo/máximo) para los ingresos de 2018
DEDUCCIÓN_TIPO = 0.10
DEDUCCIÓN_MÍN = 437
DEDUCCIÓN_MÁX = 12502

# Escala 2018 (QF)
# Cada tramo es (límite_máx, tipo). El primer tramo del 0 % termina en 9 964 €.
BAREMO_2018 = [
    (9964, 0,00),
    (27519, 0,14),
    (73779, 0,30),
    (156244, 0,41),
    (float("inf"), 0,45),
]

# Límite máximo del coeficiente familiar
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # ventaja máxima por 1/2 parte por HIJO

# Descuento (umbrales según el número de adultos / situación de pareja)
DESCUENTO_UMBRAL_SOLTERO = 1196
DECOTE_SEUIL_COUPLE = 1970

# Reducción del 20 %: umbrales en función de las partes
REDUCCIÓN_BASE_1_PARTE = 18985
REDUCCIÓN_BASE_2_PARTES = 37970
REDUCCIÓN_POR_MEDIA_CUOTA_SUP = 3803  # aumento por cada media cuota por encima de la base de 1 o 2 cuotas


# ---------- Estructuras de datos ----------

@dataclass
class ResultadoImpuesto:
    impot: int  # impuesto final a pagar, redondeado al euro inferior
    descuento: int  # descuento redondeado al euro superior
    reduction: int  # reducción del 20 %, redondeada al euro superior


# ---------- Funciones de utilidad ----------

def redondeo_al_euro_superior(euros: float) -> int:
    """Redondeo al euro superior (ceil)."""
    return int(ceil(euros))


def redondeo_inf(euros: float) -> int:
    """Redondeo al euro inferior (floor)."""
    return int(floor(euros))


# ---------- Paso 1: Renta neta imponible (RNI) ----------

def calculo_deducible(ingresos_brutos: float) -> int:
    """
    Calcula la deducción del 10 %, con un límite máximo/mínimo, REDONDEADA AL EURO SUPERIOR.
    """
    descuento_calculado = ingresos_brutos * TIPO_DESCUENTO
    descuento_calculado = max(DESCUENTO_MÍNIMO, min(DESCUENTO_MÁXIMO, descuento_calculado))
    return redondeo_al_euro_superior(descuento_calculado)


def ingresos_netos_imponibles(ingresos_brutos: float) -> int:
    """RNI = ingresos brutos - deducción (deducción redondeada al alza)."""
    return int(round(ingresos_brutos - calculo_desgravación(ingresos_brutos)))


# ---------- Paso 2: Número de partes ----------

def número_de_cuotas(adultos: int, niños: int) -> float:
    """
    Reglas: 1 parte si hay 1 adulto, 2 partes si hay 2 adultos.
    Niños: +0,5 por los dos primeros, +1 por cada niño a partir del tercero.
    (Aquí se ignoran los casos particulares, como el de padres solteros, de acuerdo con el PDF.)
    """
    porciones = 1,0 si adultos == 1; 2,0 en caso contrario
    si niños <= 0:
        return partes
    si hijos == 1:
        devuelve partes + 0.5
    # niños >= 2
    porciones += 1.0  # +0,5 +0,5 para los dos primeros
    si hijos > 2:
        porciones += (niños - 2) * 1.0  # +1 por niño a partir del tercero
    return partes


def medias_partes_hijos(hijos: int) -> int:
    """Número de MEDIAS partes que corresponden a los hijos (útil para el límite máximo de QF)."""
    si hijos <= 0:
        return 0
    si hijos == 1:
        return 1  # 0,5 parte
    # Los dos primeros hijos = 2 * media parte; a partir del tercero, +2 medias partes por hijo
    resultado 2 + (hijos - 2) * 2


# ---------- Paso 3: Impuesto bruto mediante QF ----------

def impuesto_bruto_por_partes(qf: float) -> float:
    """Impuesto por parte mediante la escala progresiva de 2018 (sin redondear)."""
    imp = 0.0
    previous = 0.0
    for límite_sup, tasa in BAREMO_2018:
        importe_tramo = max(0.0, min(qf, límite_superior) - anterior)
        imp += importe_tramo * tasa
        si qf <= límite_sup:
            break
        anterior = límite_sup
    return imp


def impuesto_bruto_total(rni: float, participaciones: float) -> float:
    """Impuesto bruto total = impuesto por participación * número de participaciones (sin redondear)."""
    if parts <= 0:
        return 0.0
    qf = rni / parts
    return impuesto_bruto_por_parte(qf) * partes


# ---------- Paso 4.1: Limitación del coeficiente familiar ----------

def aplicar_límite_qf(impuesto_con_hijos: float, rni: float, adultos: int, hijos: int) -> float:
    """
    Calcula el impuesto tras el límite máximo del coeficiente familiar si la ventaja por hijos supera el límite máximo.
    - Impuesto A: con participaciones que incluyen a los hijos
    - Impuesto B: solo con las cuotas de los adultos (1 o 2)
    - Beneficio real = B - A
    - Beneficio máximo = 1551 € por 1/2 parte NIÑO
    Si la ventaja real > ventaja máxima, impuesto = B - ventaja máxima; en caso contrario, impuesto = A.
    """
    partes_adultos = 1,0 si adultos == 1; en caso contrario, 2,0
    imp_b = impuesto_bruto_total(rni, partes_adultos)
    imp_a = impuesto_con_hijos
    ventaja_real = max(0.0, imp_b - imp_a)
    ventaja_máx = PF_QF_VENTAJA_POR_MEDIA_PARTE * medias_partes_hijos(hijos)
    si ventaja_real > ventaja_máx:
        return imp_b - ventaja_máx
    devuelve imp_a


# ---------- Paso 4.2: Descuento y reducción del 20 % ----------

def umbral_descuento(adultos: int) -> int:
    return DESCUENTO_UMBRAL_PAREJA si adultos >= 2 else DESCUENTO_UMBRAL_SOLTERO


def umbral_reduccion_20(rni: int, adultos: int, partes: float) -> int:
    """
    Umbral de elegibilidad para la reducción del 20 %:
    - Base: 18 985 € por 1 parte, 37 970 € por 2 partes
    - + 3 803 € por cada 1/2 parte adicional por encima de la base correspondiente.
    """
    base = REDUCCIÓN_BASE_2_PARTES si adultos >= 2; en caso contrario, REDUCCIÓN_BASE_1_PARTE
    base_parts = 2.0 si adultos >= 2; si no, 1.0
    media_sup = max(0, int(round((partes - base_partes) * 2)))
    return base + demi_sup * REDUCCIÓN_POR_MEDIA_PARTE_SUP


def calc_descuento_reducción(impuesto_después_límite: float, rni: int, adultos: int, partes: float):
    """
    Calcula (descuento_redondeado, reducción_redondeada, impuesto_después_de_correcciones).
    Regla de interacción: la bonificación puede aplicarse mediante repesca si, tras
    aplicación TEÓRICA de la reducción del 20 %, el impuesto quedara por debajo del umbral.
    Orden: descuento -> reducción.
    """
    imp = max(0.0, impuesto_después_del_límite)
    descuento = 0
    reducción = 0

    # Elegibilidad para la reducción del 20 % basada en la RNI
    umbral_red = umbral_reducción_20(rni, adultos, partes)
    eligible_reduction = rni < umbral_red  # «por debajo de un umbral» en el PDF

    # Elegibilidad para el descuento: directamente por debajo del umbral, o repesca si la reducción haría que se quedara por debajo del umbral
    s_dec = umbral_descuento(adultos)
    descuento_directo = imp <= s_dec
    recuperación = False
    if not direct_decote and eligible_reduction:
        imp_teórico_después_red = imp * 0.80  # reducción teórica del 20 %
        si imp_teórico_después_red <= s_dec:
            repetición = True

    elegible_descuento = (descuento_directo u oportunidad_segunda) y (imp > 0)

    # Aplicar primero el descuento (si es aplicable)
    si elegible_descuento:
        importe_descuento = s_dec - (imp * 0.75)
        descuento_calculado = max(0, redondeo_al_alto(importe_descuento))
        # El descuento no puede superar el impuesto restante: se limita
        descuento = min(descuento_calc, redondeo_al_alto(imp))
        imp = max(0.0, imp - descuento)

    # A continuación, aplicar la reducción si es aplicable
    if eligible_reduction and imp > 0:
        importe_reducción = imp * 0.20
        reducción = max(0, redondeo_al_alto(importe_reducción))
        imp = max(0.0, imp - reducción)

    return descuento, reducción, imp


# ---------- API principal ----------

def calcular_impuesto(adultos: int, niños: int, ingresos_brutos: float) -> ResultadoImpuesto:
    """
    Calcula el impuesto de 2019 (ingresos de 2018) para un hogar.
    Devuelve ResultadoImpuesto(impuesto, descuento, reducción).
    """
    # 1) RNI
    rni = ingresos_netos_imponibles(ingresos_brutos)

    # 2) Cuotas
    cuotas = número_de_cuotas(adultos, niños)

    # 3) Impuesto bruto según la tabla (con todas las cuotas)
    imp_bruto_A = impuesto_bruto_total(rni, cuotas)

    # 4.1) Límite máximo del coeficiente familiar
    imp_después_límite = aplicar_límite_coeficiente_familiar(imp_bruto_A, rni, adultos, niños)

    # 4.2) Descuento y reducción del 20 % (con repesca)
    descuento, reducción, imp_corregido = calc_descuento_reducción(imp_después_límite, rni, adultos, partes)

    # 5) Redondeo final del impuesto (↓) y limitación a 0
    imp_final = max(0, redondeo_min(imp_corrige))

    return ResultadoImpuesto(impuesto=imp_final, descuento=descuento, reducción=reducción)


# ---------- Pequeño programa de prueba ----------

def _nearly_equal(a: int, b: int, tol: int = 1) -> bool:
    return abs(a - b) <= tol


def pruebas_unitarias():
    """
    Devuelve una lista de tuplas (entradas, esperado, obtenido, ok) para cada prueba.
    Tolerancia: ±1 € en cada valor (impuesto, descuento, rebaja).
    """
    casos = [
        # (adultos, niños, ingresos) -> (impuesto, descuento, reducción)
        ((2, 2, 55555), (2815, 0, 0)),
        ((2, 2, 50000), (1385, 384, 346)),
        ((2, 3, 50000), (0, 720, 0)),
        ((1, 2, 100000), (19884, 0, 0)),
        ((1, 3, 100000), (16782, 0, 0)),
        ((2, 3, 100000), (9200, 0, 0)),
        ((2, 5, 100000), (4230, 0, 0)),
        ((1, 0, 100000), (22986, 0, 0)),
        ((2, 2, 30000), (0, 0, 0)),
        ((1, 0, 200000), (64211, 0, 0)),
        ((2, 3, 200000), (42843, 0, 0)),
        ((2, 2, 49500), (1297, 431, 324)),
    ]

    resultados = []
    for (adultos, niños, ingresos), esperado in caso:
        res = calcular_impuesto(adultos, niños, ingresos)
        obtenido = (res.impuesto, res.descuento, res.reducción)
        ok = _nearly_equal(obtenido[0], esperado[0]) and _nearly_equal(obtenido[1], esperado[1]) and _nearly_equal(obtenido[2],
                                                                                                             esperado[2])
        resultados.append(((adultos, niños, ingresos), esperado, obtenido, ok))
    return resultados


if __name__ == "__main__":
    for entradas, esperado, obtenido, ok in pruebas_unitarias():
        print(f"{entradas} -> esperado={esperado}, obtenido={obtenido} : {'OK' si ok, si no 'FALLO'}")

5.4. El problema 3

Ahora le pedimos a ChatGPT que busque en Internet las reglas para calcular el impuesto:

 

Esta vez no proporcionamos el PDF que daba las reglas de cálculo a seguir. Solo damos nuestras instrucciones en el archivo de texto. Recordemos que este archivo de texto contiene ahora 12 pruebas unitarias tras haber añadido a las 11 pruebas iniciales la utilizada por Gemini para demostrar que mi PDF inicial era erróneo.

ChatGPT responde en 8 minutos y proporciona un enlace para descargar el script generado. Una vez cargado en PyCharm, este script supera las 12 pruebas. Por lo tanto, en los dos problemas planteados, ChatGPT ha respondido correctamente a la primera, superando así a Gemini.

ChatGPT proporciona sus fuentes en su respuesta:

 

No hay nada que decir, es un trabajo magnífico.

Ahora podemos pedirle, como hicimos con Gemini, que genere un PDF para los estudiantes.

 

La respuesta de ChatGPT se obtuvo tras varios intercambios, ya que el PDF generado utilizaba una fuente que sustituía los caracteres por un cuadrado. Pero finalmente generó el PDF. Lo incluyo porque ofrece reglas diferentes a las del PDF de Gemini y me pregunté entonces quién tenía razón. Vamos a investigarlo.

 
 
 

La diferencia con el PDF de Gemini está en el cálculo del descuento. Las dos IA no tienen el mismo enfoque. Gemini había escrito:

 
 
 

Las dos IA tienen dos enfoques diferentes. ¿Quién tiene razón?

5.5. El problema 4

Vamos a pedirle a ChatGPT que utilice su PDF para calcular el impuesto:

Como en ocasiones anteriores, genera un script en Python que funciona a la primera. Habíamos añadido en las instrucciones una prueba adicional:

test13: (1, 0, 18535) -> (359, 491, 90)

Las 13 pruebas se han superado con éxito.

5.6. Volvamos a Gemini

Ahora volvemos a Gemini, al que vamos a presentar el PDF de ChatGPT. Dado que las reglas implementadas en este PDF son diferentes de las implementadas en el PDF de Gemini, cabe preguntarse qué va a pasar:

 

Gemini generó en primer lugar un script de Python que fallaba en las pruebas. Le presentamos los registros:

Pregunta 2

 

Pregunta 3

Todavía hay errores. Seguimos.

 

Pregunta 4

Sigue habiendo errores en la ejecución:

 

Esta vez está bien.

No obstante, nos intriga que, con archivos PDF que tienen reglas de cálculo bastante diferentes, ambas IA generen resultados correctos.

Le hacemos la siguiente pregunta a Gemini:

 

La pregunta completa es la siguiente:

Después de todo lo que hemos hecho, compara los dos enfoques, el tuyo y el de ChatGPT en los PDF que te adjunto [... según Gemini] para ti, [... según ChatGPT] para ChatGPT. Dime cuál debemos preferir, ya que de las 13 pruebas generadas las superas todas con uno u otro PDF.

La respuesta de Gemini es categórica:

 
 
 
 
 
 

5.7. ¿Qué opina ChatGPT?

Le hacemos a ChatGPT la misma pregunta que le hicimos a Gemini.

 

La respuesta de ChatGPT es la siguiente:

 
 

Así pues, ChatGPT nos propone una prueba unitaria para decidir entre los dos métodos. Duplicamos:

  • El script [gemini3] generado por Gemini tomando como fuente su PDF [El problema según Gemini] se duplica en el script [gemini4];
  • El script [chatGPT3] generado por ChatGPT tomando como fuente su PDF [El problema según ChatGPT] se duplica en el script [chatGPT4];

Además, añadimos en cada uno de los scripts [gemini4, chatGPT4] la prueba unitaria propuesta por ChatGPT para decidir entre las dos IA.

La ejecución de [gemini4] da los siguientes resultados:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:/Program Files/JetBrains/PyCharm 2025.2.1.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py" 
La ejecución de las pruebas comenzó a las 17:45...
Iniciando pruebas unitarias con los argumentos python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py en C:\Data\st-2025\dev\python\code\python-flask-2025-cours

Error en SubTest: Traceback (última llamada más reciente):
  Archivo «C:\Program Files\Python313\Lib\unittest\case.py», línea 58, en testPartExecutor
    yield
  Archivo "C:\Program Files\Python313\Lib\unittest\case.py", línea 556, en subTest
    yield
  Archivo "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py", línea 234, en test_cas_verifies_simulateur_officiel
    self.assertAlmostEqual(calcul_impot, attendu_impot, delta=1, msg="Fallo en el importe del impuesto")
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2669 != 2270 dentro de 1 delta (399 de diferencia): Fallo en el importe del impuesto




Se ha ejecutado 1 prueba en 0,010 s

FALLÓ (fallos=1)

Una o más subpruebas han fallado
Lista de subpruebas fallidas: [Prueba «test12» con entrada (2, 0, 43333)]

El proceso finalizó con el código de salida 1

Por lo tanto, Gemini no supera la prueba añadida por ChatGPT.

La ejecución de [chatGPT4] da los siguientes resultados:


C:\Data\st-2025\dev\python\code\python-flask-2025-cours\.venv\Scripts\python.exe "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT4.py" 
Prueba (2, 2, 55555) -> obtenido (impuesto=2814, descuento=0, reducción=0) | esperado (2815, 0, 0) | OK
Prueba (2, 2, 50000) -> obtenido (impuesto=1384, descuento=384, reducción=347) | esperado (1385, 384, 346) | OK
Prueba (2, 3, 50000) -> resultado (impuesto=0, descuento=721, reducción=0) | esperado (0, 720, 0) | OK
Prueba (1, 2, 100000) -> resultado (impuesto=19884, descuento=0, reducción=0) | esperado (19884, 0, 0) | OK
Prueba (1, 3, 100000) -> resultado (impuesto=16782, descuento=0, reducción=0) | esperado (16782, 0, 0) | OK
Prueba (2, 3, 100000) -> resultado (impuesto=9200, descuento=0, reducción=0) | esperado (9200, 0, 0) | OK
Prueba (2, 5, 100000) -> resultado (impuesto=4230, descuento=0, reducción=0) | esperado (4230, 0, 0) | OK
Prueba (1, 0, 100000) -> resultado (impuesto=22986, descuento=0, reducción=0) | esperado (22986, 0, 0) | OK
Prueba (2, 2, 30000) -> resultado (impuesto=0, descuento=0, reducción=0) | esperado (0, 0, 0) | OK
Prueba (1, 0, 200000) -> resultado (impuesto=64210, descuento=0, reducción=0) | esperado (64211, 0, 0) | OK
Prueba (2, 3, 200000) -> resultado (impuesto=42842, descuento=0, reducción=0) | esperado (42843, 0, 0) | OK
Prueba (2, 2, 49500) -> resultado (impuesto=1296, descuento=431, reducción=325) | esperado (1297, 431, 324) | OK
Prueba (1, 0, 18535) -> resultado (impuesto=359, descuento=491, reducción=90) | esperado (359, 491, 90) | OK
Prueba (2, 0, 43333) -> resultado (impuesto=2268, descuento=0, reducción=401) | esperado (2270, 0, 400) | FALLO
 Detalles de tolerancia ±1 €: ¿impuesto correcto? Falso, ¿descuento correcto? Verdadero, ¿reducción correcta? Verdadero

Resultado global: AL MENOS UNA PRUEBA HA FALLADO ❌

Proceso finalizado con código de salida 0

ChatGPT también falla en la prueba añadida, pero no por las mismas razones que Gemini. ChatGPT ha encontrado los resultados correctos, pero con una diferencia de 2 euros en lugar del 1 euro establecido.

Así que a partir de ahora utilizaremos el PDF generado por ChatGPT con las siguientes IA. Cabe señalar que es debido a la falta de pruebas unitarias propuestas en mis instrucciones que ambas IA superaron las primeras pruebas. De ahí, en este ejemplo concreto, la importancia de incluir pruebas unitarias para los casos límite del cálculo del impuesto. Como es bastante difícil imaginar estas pruebas por uno mismo. Vamos a pedir a las IA que añadan ellas mismas algunas.

5.8. El problema 3 con pruebas unitarias generadas por las IA

Los resultados obtenidos con Gemini y ChatGPT dejan lugar a dudas. ¿Han encontrado las IA una solución general que valide todas las pruebas imaginables o han encontrado una solución que valide únicamente las pruebas impuestas? Vamos a volver a partir de una solución sin PDF para obligar a las IA a buscar en Internet la información que necesitan. Y modificamos nuestras instrucciones de la siguiente manera:

 

El archivo de texto [instructionsSansPDF4.txt] ya contiene 14 pruebas impuestas. A estas pruebas, añadimos las siguientes instrucciones:


7 - Añadirás tantas pruebas unitarias como sean necesarias para verificar los casos límite del cálculo del impuesto.

En cuanto al código, completarás el siguiente script al que habrás añadido tus propias pruebas.

# =========================
# Pruebas unitarias (tolerancia de ±1 €)
# =========================

PRUEBAS = [
    # (adultos, niños, ingresos) -> (impuesto, descuento, reducción)
    ((2, 2, 55555), (2815, 0, 0)),
    ((2, 2, 50000), (1385, 384, 346)),
    ((2, 3, 50000), (0, 720, 0)),
    ((1, 2, 100000), (19884, 0, 0)),
    ((1, 3, 100000), (16782, 0, 0)),
    ((2, 3, 100000), (9200, 0, 0)),
    ((2, 5, 100000), (4230, 0, 0)),
    ((1, 0, 100000), (22986, 0, 0)),
    ((2, 2, 30000), (0, 0, 0)),
    ((1, 0, 200000), (64211, 0, 0)),
    ((2, 3, 200000), (42843, 0, 0)),
    ((2, 2, 49500), (1297, 431, 324)),
    ((1, 0, 18535), (359, 491, 90)),
    ((2, 0, 43333), (2270, 0, 400)),
]


def _ok(a, b, tol=1):
    return abs(a - b) <= tol


def run_tests(verbose: bool = True) -> bool:
    all_ok = True
    for (params, expected) in TESTS:
        a, e, r = params
        impuesto_esperado, descuento_esperado, reducción_esperada = esperado
        res = calcul_impot_2019(a, e, r)
        ok_impot = _ok(res.impot, exp_impot)
        ok_descuento = _ok(res.descuento, exp_descuento)
        ok_reduc = _ok(res.reducción, exp_reduc)
        test_ok = ok_impot and ok_decote and ok_reduc
        si verboso:
            print(
                f"Prueba {params} -> obtenido (impuesto={res.impot}, descuento={res.decote}, reducción={res.reduction}) | esperado {expected} | {'OK' si test_ok, de lo contrario 'FALLO'}")
            si no test_ok:
                imprimir(
                    f" Detalles de tolerancia ±1 €: ¿impuesto correcto? {ok_impot}, ¿descuento correcto? {ok_decote}, ¿reducción correcta? {ok_reduc}")
        all_ok &= test_ok
    if verbose:
        print("\nResultado global:", "TODAS LAS PRUEBAS SUPERADAS ✅" if all_ok else "AL MENOS UNA PRUEBA FALLIDA ❌")
    return all_ok


if __name__ == "__main__":
    run_tests()
  • Líneas 11-24, las 14 pruebas obligatorias;
  • Líneas 5-55: este código proviene del script generado por ChatGPT. Vamos a obligar a Gemini a utilizar este código para facilitar las comparaciones entre los dos scripts generados.

Empezamos con ChatGPT:

 

Su primera respuesta es incorrecta. Se lo digo proporcionándole los registros de la ejecución:

Su segunda respuesta es la correcta. ChatGPT ha añadido las siguientes 11 pruebas a las 14 pruebas impuestas:

# Casos límite adicionales (bordes de escalones/redondeos)
TESTS += [
    # Descuento del 10 %: mínimo y máximo
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < mínimo 437 => RNI bajo -> impuesto nulo
    ((1, 0, 200000), (64211, 0, 0)),  # el límite máximo de la deducción ya está cubierto en las pruebas iniciales

    # Descuento: justo por debajo/por encima de los umbrales
    ((1, 0, 25000), None),  # diagnóstico
    ((2, 0, 35000), None),  # diagnóstico

    # Reducción del 20 %: derecho pleno frente a recorte
    ((1, 0, 17000), None),  # diagnóstico
    ((2, 0, 34000), None),  # diagnóstico
    ((1, 0, 20000), None),  # diagnóstico
    ((2, 0, 40000), None),  # diagnóstico

    # Cambio de cuotas (limitación de QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Ahora hay 25 pruebas unitarias. He comprobado manualmente las 11 pruebas nuevas con el simulador oficial de la DGIP y todo está bien.

Ahora pasamos a Gemini. Esto va a ser mucho más complicado. Conseguirá generar un script que supere las 25 pruebas de ChatGPT, pero tras un largo proceso de depuración.

 

A continuación, la lista de depuración:

 

Curiosamente, la mayoría de las pruebas han fallado, incluso entre las 14 obligatorias, cuando en el pasado Gemini había generado código que las superaba todas.

La siguiente respuesta de Gemini sigue sin ser correcta:

 

La siguiente respuesta tampoco:

 

La siguiente respuesta tampoco. Así que cambio de estrategia. Le pido que supere las 25 pruebas que superó ChatGPT adjuntándole los registros de ChatGPT:

 

Gemini falla. Sí que ha añadido las pruebas de ChatGPT. Le adjunto los registros de su ejecución:

 

Sigue sin hacerlo:

 

Sigue sin funcionar:

 

Sigue sin funcionar:

 

Sigue sin funcionar, pero es mejor:

 

Gemini comete nuevos errores:

 

Vuelve a mejorar:

 

Esta vez sí:

Sin duda, en este ejemplo concreto del cálculo del impuesto de 2019 con las restricciones incluidas en el archivo de instrucciones, ChatGPT ha sido más acertado que Gemini. Pero esto es solo un ejemplo.

Podemos ir más allá. Podemos pedirle a Gemini que vuelva a generar un PDF según las reglas de cálculo que utilizó para superar las 25 pruebas. Queremos ver si ha cambiado su razonamiento inicial sobre los cálculos de la bonificación y la reducción del 20 %:

Esta vez, Gemini generó un archivo MarkDown que luego convertí en PDF [El problema según Gemini versión 2]. Y Gemini efectivamente ha cambiado su razonamiento:

 
 

Se observa que ya no aparece el cálculo específico del descuento ni la regla de recuperación. Gemini ha adoptado ahora el razonamiento de ChatGPT.