Skip to content

5. Resolución de los tres problemas con ChatGPT

5.1. Introducción

Aquí hay una primera captura de pantalla de una sesión de ChatGPT:

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

ChatGPT es un producto derivado de OpenAI, disponible en URL y [https://chatgpt.com/]. Para tener un historial de tus sesiones de preguntas y respuestas como el anterior, debes crear una cuenta. Además, al igual que todas las demás versiones de IA que se han probado, 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 ofrece la opción de continuarla más adelante. 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 IA;

5.2. El problema 1

La pregunta en 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 el [1], se incluyó el PDF generado por Gemini;
  • En [2], se agregó la prueba unitaria con la que Gemini demostró su superioridad:
test12 : (2, 2, 49500) -> (1297, 431, 324)

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

 

El script [chatGPT1] funciona a la primera. No hay duda: en este problema, ChatGPT tuvo un mejor desempeño que Gemini.

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

# -*- coding: utf-8 -*-
"""
Calcul de l'impôt 2019 (revenus 2018) en Python.

Hypothèses et règles (d'après le PDF fourni) :
- Tous les revenus sont portés sur le déclarant 1 (même pour un couple).
- Abattement forfaitaire de 10 % encadré par un minimum et un maximum.
- Barème progressif 2018 appliqué au quotient familial (QF).
- Plafonnement du quotient familial : avantage par demi-part enfant limité.
- Décote et réduction de 20 % pour faibles revenus, avec la règle de repêchage.
- Ordre des correctifs : plafonnement QF -> décote -> réduction 20 %.
- Arrondis : abattement (↑), décote (↑), réduction (↑), impôt final (↓).
- On renvoie l'impôt à payer (arrondi à l'euro inf.), la décote et la réduction
  (arrondies à l'euro sup.).

Les nombres “magiques” sont regroupés en constantes, et chaque étape est isolée
dans des fonctions pour la clarté.
"""

from dataclasses import dataclass
from math import ceil, floor

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

# Deducción por gastos profesionales (mínimo/máximo) para los ingresos de 2018
ABATTEMENT_TAUX = 0.10
ABATTEMENT_MIN = 437
ABATTEMENT_MAX = 12502

# Escala de 2018 (QF)
# Cada tramo es (borne_sup, tasa). El primer tramo del 0 % termina en 9 964 €.
BAREME_2018 = [
    (9964, 0.00),
    (27519, 0.14),
    (73779, 0.30),
    (156244, 0.41),
    (float("inf"), 0.45),
]

# Límite máximo del cociente familiar
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # beneficio máximo por 1/2 parte ENFANT

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

# Reducción del 20 %: umbrales según las partes
REDUCTION_BASE_1_PART = 18985
REDUCTION_BASE_2_PARTS = 37970
REDUCTION_PAR_DEMI_PART_SUP = 3803  # Añadido por cada 1/2 parte más allá de la base de 1 o 2 partes


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

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


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

def arrondi_sup(euros: float) -> int:
    """Arrondi à l'euro supérieur (ceil)."""
    return int(ceil(euros))


def arrondi_inf(euros: float) -> int:
    """Arrondi à l'euro inférieur (floor)."""
    return int(floor(euros))


# ---------- Paso 1: Ingreso neto imponible (RNI) ----------

def calcul_abattement(revenus_bruts: float) -> int:
    """
    Calcule l'abattement de 10 %, plafonné/planche, ARRONDI À L'EURO SUPÉRIEUR.
    """
    abattement_calcule = revenus_bruts * ABATTEMENT_TAUX
    abattement_calcule = max(ABATTEMENT_MIN, min(ABATTEMENT_MAX, abattement_calcule))
    return arrondi_sup(abattement_calcule)


def revenu_net_imposable(revenus_bruts: float) -> int:
    """RNI = revenus bruts - abattement (abattement arrondi ↑)."""
    return int(round(revenus_bruts - calcul_abattement(revenus_bruts)))


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

def nombre_parts(adultes: int, enfants: int) -> float:
    """
    Règles : 1 part si 1 adulte, 2 parts si 2 adultes.
    Enfants : +0,5 pour les 2 premiers, +1 par enfant à partir du 3e.
    (On ignore ici tout cas particulier type parent isolé, conformément au PDF.)
    """
    parts = 1.0 if adultes == 1 else 2.0
    if enfants <= 0:
        return parts
    if enfants == 1:
        return parts + 0.5
     # hijos >= 2
    parts += 1.0  # +0,5 +0,5 por los dos primeros
    if enfants > 2:
        parts += (enfants - 2) * 1.0  # +1 por hijo a partir del tercero
    return parts


def demi_parts_enfants(enfants: int) -> int:
    """Nombre de DEMI-parts dû aux enfants (utile pour le plafonnement QF)."""
    if enfants <= 0:
        return 0
    if enfants == 1:
        return 1  # 0,5 parte
     # Los dos primeros hijos = 2 * media parte, luego +2 medias partes por hijo a partir del tercero
    return 2 + (enfants - 2) * 2


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

def impot_brut_par_parts(qf: float) -> float:
    """Impôt par part via barème progressif 2018 (sans arrondi)."""
    imp = 0.0
    previous = 0.0
    for borne_sup, taux in BAREME_2018:
        montant_tranche = max(0.0, min(qf, borne_sup) - previous)
        imp += montant_tranche * taux
        if qf <= borne_sup:
            break
        previous = borne_sup
    return imp


def impot_brut_total(rni: float, parts: float) -> float:
    """Impôt brut total = impôt par part * nombre de parts (sans arrondi)."""
    if parts <= 0:
        return 0.0
    qf = rni / parts
    return impot_brut_par_parts(qf) * parts


# ---------- Paso 4.1: Límite máximo del cociente familiar ----------

def apply_plafonnement_qf(impot_avec_enfants: float, rni: float, adultes: int, enfants: int) -> float:
    """
    Calcule l'impôt après plafonnement QF si l'avantage des enfants dépasse le plafond.
    - Impôt A : avec parts incluant enfants
    - Impôt B : avec seulement parts des adultes (1 ou 2)
    - Avantage réel = B - A
    - Avantage maximal = 1551 € par 1/2 part ENFANT
    Si avantage réel > avantage max, impôt = B - avantage max, sinon impôt = A.
    """
    parts_adultes = 1.0 if adultes == 1 else 2.0
    imp_b = impot_brut_total(rni, parts_adultes)
    imp_a = impot_avec_enfants
    avantage_reel = max(0.0, imp_b - imp_a)
    avantage_max = PF_QF_AVANTAGE_PAR_DEMI_PART * demi_parts_enfants(enfants)
    if avantage_reel > avantage_max:
        return imp_b - avantage_max
    return imp_a


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

def seuil_decote(adultes: int) -> int:
    return DECOTE_SEUIL_COUPLE if adultes >= 2 else DECOTE_SEUIL_CELIB


def seuil_reduction_20(rni: int, adultes: int, parts: float) -> int:
    """
    Seuil d'éligibilité à la réduction de 20 % :
    - Base : 18 985 € pour 1 part, 37 970 € pour 2 parts
    - + 3 803 € par 1/2 part supplémentaire au-delà de la base correspondante.
    """
    base = REDUCTION_BASE_2_PARTS if adultes >= 2 else REDUCTION_BASE_1_PART
    base_parts = 2.0 if adultes >= 2 else 1.0
    demi_sup = max(0, int(round((parts - base_parts) * 2)))
    return base + demi_sup * REDUCTION_PAR_DEMI_PART_SUP


def calc_decote_reduction(impot_apres_plaf: float, rni: int, adultes: int, parts: float):
    """
    Calcule (decote_arrondie, reduction_arrondie, impot_apres_correctifs).
    Règle d'interaction : la décote peut s'appliquer via repêchage si, après
    application THÉORIQUE de la réduction de 20 %, l'impôt passerait sous le seuil.
    Ordre : décote -> réduction.
    """
    imp = max(0.0, impot_apres_plaf)
    decote = 0
    reduction = 0

     # Elegibilidad para la reducción del 20 % basada en RNI
    seuil_red = seuil_reduction_20(rni, adultes, parts)
    eligible_reduction = rni < seuil_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 = seuil_decote(adultes)
    direct_decote = imp <= s_dec
    repechage = False
    if not direct_decote and eligible_reduction:
        imp_theorique_apres_red = imp * 0.80  # Reducción teórica del 20 %
        if imp_theorique_apres_red <= s_dec:
            repechage = True

    elig_decote = (direct_decote or repechage) and (imp > 0)

     # Aplicar primero la reducción (si es elegible)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
         # La deducción no puede exceder el impuesto restante: se limita
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

     # Luego se aplica la reducción, si es aplicable
    if eligible_reduction and imp > 0:
        montant_reduction = imp * 0.20
        reduction = max(0, arrondi_sup(montant_reduction))
        imp = max(0.0, imp - reduction)

    return decote, reduction, imp


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

def calcule_impot(adultes: int, enfants: int, revenus_bruts: float) -> ResultatImpot:
    """
    Calcule l'impôt 2019 (revenus 2018) pour un foyer.
    Retourne ResultatImpot(impot, decote, reduction).
    """
     # 1) RNI
    rni = revenu_net_imposable(revenus_bruts)

     # 2) Cuotas
    parts = nombre_parts(adultes, enfants)

     # 3) Impuesto bruto según la tabla (con todas las partes)
    imp_brut_A = impot_brut_total(rni, parts)

     # 4.1) Límite máximo del cociente familiar
    imp_apres_plaf = apply_plafonnement_qf(imp_brut_A, rni, adultes, enfants)

     # 4.2) Descuento y luego reducción del 20 % (con repesca)
    decote, reduction, imp_corrige = calc_decote_reduction(imp_apres_plaf, rni, adultes, parts)

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

    return ResultatImpot(impot=imp_final, decote=decote, reduction=reduction)


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

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


def tests_unitaires():
    """
    Renvoie une liste de tuples (inputs, attendu, obtenu, ok) pour chaque test.
    Tolérance : ±1 € sur chaque valeur (impôt, décote, réduction).
    """
    cas = [
         # (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)),
    ]

    resultats = []
    for (adultes, enfants, revenus), attendu in cas:
        res = calcule_impot(adultes, enfants, revenus)
        obtenu = (res.impot, res.decote, res.reduction)
        ok = _nearly_equal(obtenu[0], attendu[0]) and _nearly_equal(obtenu[1], attendu[1]) and _nearly_equal(obtenu[2],
                                                                                                             attendu[2])
        resultats.append(((adultes, enfants, revenus), attendu, obtenu, ok))
    return resultats


if __name__ == "__main__":
    for inputs, attendu, obtenu, ok in tests_unitaires():
        print(f"{inputs} -> attendu={attendu}, obtenu={obtenu} : {'OK' if ok else 'ECHEC'}")

5.4. El problema 3

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

 

Esta vez no se proporciona el PDF que daba las reglas de cálculo a seguir. Solo damos nuestras instrucciones en el archivo de texto. Recordamos que este archivo de texto contiene ahora 12 pruebas unitarias, tras haber agregado a las 11 pruebas iniciales la que utilizó 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, ante los dos problemas planteados, ChatGPT respondió correctamente a la primera, superando así a Gemini.

ChatGPT proporciona sus fuentes en su respuesta:

 

No hay nada que decir, es un excelente trabajo.

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

 

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

 
 
 

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

 
 
 

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

5.5. El problema 4

Le pediremos a ChatGPT que utilice su PDF para calcular el impuesto:

Al igual que en ocasiones anteriores, genera un script de Python que funciona a la primera. Habíamos agregado una prueba adicional en las instrucciones:

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

Las 13 pruebas se han superado con éxito.

5.6. De vuelta a Gemini

Ahora regresamos a Gemini, al que le presentaremos el PDF derivado de ChatGPT. Dado que las reglas implementadas en este PDF son diferentes de las implementadas en el PDF de Gemini, cabe preguntarse qué sucederá:

 

Gemini generó primero un script de Python que fallaba en las pruebas. Se le presentaron los registros:

Pregunta 2

 

Pregunta 3

Todavía hay errores. Seguimos adelante.

 

Pregunta 4

Sigue habiendo errores de ejecución:

 

Esta vez sí está bien.

Sin embargo, nos intriga que, a pesar de que los PDF tienen reglas de cálculo bastante diferentes, los IA generen ambos resultados correctos.

Le hacemos la siguiente pregunta a Gemini:

 

La pregunta completa es la siguiente:

Après tout ce qu'on a fait, compare les deux approches, la tienne et celle de ChatGPT dans les pDF que je te joins [.. selon Gemini] pour toi, [.. selon ChatGPT] pour ChatGPT. Dis-moi laquelle on doit préférer vu que sur les 13 tests générés tu les réussis tous avec l'un ou avec l'autre 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:

 
 

Por lo tanto, 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 [Le problème selon Gemini] se duplica en el script [gemini4];
  • El script [chatGPT3], generado por ChatGPT tomando como fuente sus scripts PDF y [Le problème selon ChatGPT], se duplica en el script [chatGPT4];

Además, se agrega en cada uno de los scripts [gemini4, chatGPT4] la prueba unitaria propuesta por ChatGPT para distinguir entre los dos IA.

La ejecución de [gemini4] arroja 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" 
Testing started at 17:45 ...
Launching unittests with arguments python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py in C:\Data\st-2025\dev\python\code\python-flask-2025-cours

SubTest failure: Traceback (most recent call last):
  File "C:\Program Files\Python313\Lib\unittest\case.py", line 58, in testPartExecutor
    yield
  File "C:\Program Files\Python313\Lib\unittest\case.py", line 556, in subTest
    yield
  File "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini4.py", line 234, in test_cas_verifies_simulateur_officiel
    self.assertAlmostEqual(calcul_impot, attendu_impot, delta=1, msg="Échec sur le montant de l'impôt")
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 2669 != 2270 within 1 delta (399 difference) : Échec sur le montant de l'impôt




Ran 1 test in 0.010s

FAILED (failures=1)

One or more subtests failed
Failed subtests list: [Test 'test12' avec entrée (2, 0, 43333)]

Process finished with exit code 1

Por lo tanto, Gemini no pasa la prueba agregada por ChatGPT.

Al ejecutar [chatGPT4] se obtienen 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" 
Test (2, 2, 55555) -> obtenu (impôt=2814, décote=0, réduction=0) | attendu (2815, 0, 0) | OK
Test (2, 2, 50000) -> obtenu (impôt=1384, décote=384, réduction=347) | attendu (1385, 384, 346) | OK
Test (2, 3, 50000) -> obtenu (impôt=0, décote=721, réduction=0) | attendu (0, 720, 0) | OK
Test (1, 2, 100000) -> obtenu (impôt=19884, décote=0, réduction=0) | attendu (19884, 0, 0) | OK
Test (1, 3, 100000) -> obtenu (impôt=16782, décote=0, réduction=0) | attendu (16782, 0, 0) | OK
Test (2, 3, 100000) -> obtenu (impôt=9200, décote=0, réduction=0) | attendu (9200, 0, 0) | OK
Test (2, 5, 100000) -> obtenu (impôt=4230, décote=0, réduction=0) | attendu (4230, 0, 0) | OK
Test (1, 0, 100000) -> obtenu (impôt=22986, décote=0, réduction=0) | attendu (22986, 0, 0) | OK
Test (2, 2, 30000) -> obtenu (impôt=0, décote=0, réduction=0) | attendu (0, 0, 0) | OK
Test (1, 0, 200000) -> obtenu (impôt=64210, décote=0, réduction=0) | attendu (64211, 0, 0) | OK
Test (2, 3, 200000) -> obtenu (impôt=42842, décote=0, réduction=0) | attendu (42843, 0, 0) | OK
Test (2, 2, 49500) -> obtenu (impôt=1296, décote=431, réduction=325) | attendu (1297, 431, 324) | OK
Test (1, 0, 18535) -> obtenu (impôt=359, décote=491, réduction=90) | attendu (359, 491, 90) | OK
Test (2, 0, 43333) -> obtenu (impôt=2268, décote=0, réduction=401) | attendu (2270, 0, 400) | ECHEC
 Détails tolérance ±1€ : impôt ok? False, décote ok? True, réduction ok? True

Résultat global : AU MOINS UN TEST ÉCHOUE ❌

Process finished with exit code 0

ChatGPT tampoco supera la prueba agregada, pero no por las mismas razones que Gemini. ChatGPT encontró los resultados correctos, pero con una diferencia de 2 euros en lugar del 1 euro exigido.

Por lo tanto, a partir de ahora utilizaremos el PDF generado por ChatGPT junto con los siguientes IA. Cabe señalar que, debido a la falta de pruebas unitarias en mis instrucciones, los dos IA superaron las primeras pruebas. De ahí la importancia, en este ejemplo concreto, de incluir pruebas unitarias para los casos límite del cálculo del impuesto. Ya que es bastante difícil imaginar estas pruebas por uno mismo. Vamos a pedirles a los IA que agreguen algunas por su cuenta.

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

Los resultados obtenidos con Gemini y ChatGPT generan dudas. ¿Han encontrado las IA una solución general que supere todas las pruebas imaginables, o han encontrado una solución que solo supere las pruebas impuestas? Vamos a volver a partir de una solución sin PDF para obligar a los 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 obligatorias. A estas pruebas, añadimos las siguientes instrucciones:


7 - tu ajouteras autant de tests unitaires que nécessaires pour vérifier les cas limites du calcul de l'impôt.

Pour le code tu complèteras le script suivant auquel tu auras rajouté tes propres tests.

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

TESTS = [
     # (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
        exp_impot, exp_decote, exp_reduc = expected
        res = calcul_impot_2019(a, e, r)
        ok_impot = _ok(res.impot, exp_impot)
        ok_decote = _ok(res.decote, exp_decote)
        ok_reduc = _ok(res.reduction, exp_reduc)
        test_ok = ok_impot and ok_decote and ok_reduc
        if verbose:
            print(
                f"Test {params} -> obtenu (impôt={res.impot}, décote={res.decote}, réduction={res.reduction}) | attendu {expected} | {'OK' if test_ok else 'ECHEC'}")
            if not test_ok:
                print(
                    f" Détails tolérance ±1€ : impôt ok? {ok_impot}, décote ok? {ok_decote}, réduction ok? {ok_reduc}")
        all_ok &= test_ok
    if verbose:
        print("\nRésultat global :", "TOUS LES TESTS PASSENT ✅" if all_ok else "AU MOINS UN TEST ÉCHOUE ❌")
    return all_ok


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

Comenzamos con ChatGPT:

 

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

Su segunda respuesta es la correcta. ChatGPT agregó las siguientes 11 pruebas a las 14 pruebas obligatorias:

# Casos límite adicionales (límites de tramos/redondeos)
TESTS += [
     # Descuento del 10 %: mínimo y máximo
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < límite mínimo de 437 => RNI bajo -> impuesto nulo
    ((1, 0, 200000), (64211, 0, 0)),  # El límite máximo de la deducción ya se cubrió 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 participaciones (límite máximo QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

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

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

 

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

 

Curiosamente, la mayoría de las pruebas fallaron, incluso entre las 14 obligatorias, mientras que 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 agregó las pruebas de ChatGPT. Le adjunto los registros de su ejecución:

 

Sigue sin funcionar:

 

Todavía no:

 

Todavía no:

 

Todavía no, pero está mejor:

 

Gemini comete nuevos errores:

 

Vuelve a mejorar:

 

Esta vez sí:

Sin lugar a dudas, en este ejemplo específico del cálculo del impuesto de 2019 con las restricciones establecidas en el archivo de instrucciones, ChatGPT fue más preciso 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 deducción y la reducción del 20 %:

Esta vez, Gemini generó un archivo MarkDown que luego transformé en PDF [Le problème selon Gemini version 2]. Y Gemini efectivamente cambió su razonamiento:

 
 

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