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], el version de ChatGPT utilizado;

ChatGPT es un producto de OpenAI disponible en URL [https://chatgpt.com/]. Para disponer de un historial de sus sesiones de preguntas y respuestas como el anterior, debe 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 le propone 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 [1], se ha introducido el PDF generado por Gemini;
  • En [2], se ha añadido la prueba unitaria con la que Gemini ha demostrado su superioridad:
test12 : (2, 2, 49500) -> (1297, 431, 324)

Ejecutamos 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. En este caso no hay color: en este problema, ChatGPT ha funcionado mejor 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

# Baremo 2018 (QF)
# Cada tramo es (borne_sup, tipo). 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 coeficiente familiar
PF_QF_AVANTAGE_PAR_DEMI_PART = 1551  # ventaja máxima 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 en función de 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 por encima 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: Renta Net 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 a través de 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 coeficiente 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 superara el 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 bonificación (si es aplicable)
    if elig_decote:
        montant_decote = s_dec - (imp * 0.75)
        decote_calc = max(0, arrondi_sup(montant_decote))
         # La deducción no puede superar el impuesto restante: se limita
        decote = min(decote_calc, arrondi_sup(imp))
        imp = max(0.0, imp - decote)

     # A continuación, aplicar 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 baremo (con todas las partes)
    imp_brut_A = impot_brut_total(rni, parts)

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

     # 4.2) Descuento y posterior 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 de cálculo del impuesto:

 

En esta ocasión no se incluye el archivo PDF, que contenía las reglas de cálculo que debían seguirse. Solo se proporcionan nuestras instrucciones en el archivo de texto. Recordamos 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, a las dos preguntas planteadas, ChatGPT respondió 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 intentos, 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 al PDF de Gemini y me pregunté entonces quién tenía razón. Vamos a investigar.

 
 
 

La diferencia con el PDF de Gemini está 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

Vamos a pedirle 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 añadido una prueba adicional en las instrucciones:

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

Las 13 pruebas se han superado con éxito.

5.6. Volvemos 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. Se le presentaron los registros:

Pregunta 2

 

Pregunta 3

Todavía hay errores. Seguimos.

 

Pregunta 4

Sigue habiendo errores de ejecución:

 

Esta vez sí.

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

Planteamos 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 añade en cada uno de los scripts [gemini4, chatGPT4] la prueba unitaria propuesta por ChatGPT para decidir entre los 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" 
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 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" 
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 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 exigido.

Por lo tanto, a partir de ahora utilizaremos el PDF generado por ChatGPT 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 resulta bastante difícil imaginar estas pruebas por uno mismo. Vamos a pedir a los IA que añadan ellos mismos algunas.

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

Los resultados obtenidos con Gemini y ChatGPT dejan dudas. ¿Han encontrado los 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 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 impuestas. 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 obligatorias;
  • Líneas 5-55: este código procede 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 indico 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 obligatorias:

# Cas limites supplémentaires (bords de paliers/arrondis)
TESTS += [
    # Abattement 10 % : plancher et plafond
    ((1, 0, 3000), (0, 0, 0)),  # 10 % = 300 < plancher 437 => RNI faible -> impôt nul
    ((1, 0, 200000), (64211, 0, 0)),  # plafond abattement déjà couvert dans tests initiaux

    # Décote : juste en dessous / au-dessus des seuils
    ((1, 0, 25000), None),  # diagnostique
    ((2, 0, 35000), None),  # diagnostique

    # Réduction 20 % : plein droit vs écrêtement
    ((1, 0, 17000), None),  # diagnostique
    ((2, 0, 34000), None),  # diagnostique
    ((1, 0, 20000), None),  # diagnostique
    ((2, 0, 40000), None),  # diagnostique

    # Changement de parts (plafonnement QF)
    ((2, 1, 80000), None),
    ((2, 2, 80000), None),
    ((2, 3, 80000), None),
]

Ahora hay 25 pruebas unitarias. He comprobado manualmente las 11 nuevas pruebas con el simulador oficial de 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 proceso de depuración de long.

 

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

 

Curiosamente, la mayoría de las pruebas han fallado, 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 ha añadido las pruebas de ChatGPT. Le adjunto los registros de su ejecución:

 

Sigue sin funcionar:

 

Sigue sin funcionar:

 

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 concreto del cálculo del impuesto de 2019 con las restricciones establecidas en el archivo de instrucciones, ChatGPT ha sido 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 bonificación y la reducción del 20 %:

Esta vez, Gemini ha generado un archivo MarkDown que luego he transformado en PDF [Le problème selon Gemini version 2]. Y Gemini efectivamente ha cambiado 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.