Skip to content

4. Resolución de los tres problemas con Google Gemini

Vamos a mostrar las capturas de pantalla de las tres sesiones de Gemini que permitieron resolver los tres problemas planteados. Entraremos bastante en detalle. Una vez hecho esto, no repetiremos el proceso para las demás IA probadas. Funcionan de manera análoga. Solo daremos los detalles más destacados.

4.1. Introducción

Recordamos la primera captura de pantalla de Gemini mostrada anteriormente:

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

Gemini es un producto de Google disponible en la URL [https://gemini.google.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, Gemini 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 tarde. Como resulta bastante frustrante tener que parar en medio de una sesión, me suscribí. Por suerte, el primer mes de la suscripción a Gemini es gratuito. Hice lo mismo con las otras IA que tenían estas limitaciones, a saber, ChatGPT, MistralAI y ClaudeAI. Contraté una suscripción de un mes, pero en este caso el primer mes era de pago. No encontré limitaciones con Grok. DeepSeek no anuncia limitaciones, pero a veces responde [Server busy] e interrumpe la sesión. Es como poner límites sin decirlo.

En lo que sigue, hablaré de «sesión de preguntas y respuestas», abreviada simplemente como «sesión». Las IA suelen utilizar el término inglés «chat» (charla) o «conversación».

La interfaz de Gemini para hacer una pregunta es la siguiente:

  • En [1], tu pregunta;
  • En [2], el icono que activa la IA para calcular la respuesta;
  • En [3-4], puedes adjuntar archivos; ## 4.2. El problema 1

La sesión para el problema 1 es la siguiente:

 
  • En [1], la pregunta;
  • En [2], el inicio de la respuesta de Gemini;

El resto de la respuesta es el siguiente:

 
 
 
 

La respuesta es correcta. Las otras cinco IA también darán la respuesta correcta de forma similar.

4.3. El problema 2

4.3.1. Introducción

Recordemos aquí el problema inicial del curso [python3-flask-2020]. Se trata de un texto que se entrega a los estudiantes en las clases prácticas.

 

La tabla anterior permite calcular el impuesto en el caso simplificado de un contribuyente que solo tiene que declarar su salario. Como indica la nota (1), el impuesto así calculado es el impuesto antes de aplicar tres mecanismos:

  • El límite máximo del coeficiente familiar que se aplica a las rentas altas;
  • La bonificación y la reducción de impuestos que se aplican a las rentas bajas;

Así, el cálculo del impuesto comprende los siguientes pasos [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

 

Nos proponemos escribir un programa que permita calcular el impuesto de un contribuyente en 2019 en el caso simplificado de un contribuyente que solo tenga que declarar su salario.

4.3.1.1. Cálculo del impuesto bruto

El impuesto bruto se puede calcular de la siguiente manera:

En primer lugar, se calcula el número de participaciones del contribuyente:

  • Cada progenitor aporta 1 parte;
  • Los dos primeros hijos aportan cada uno 1/2 parte;
  • Los hijos siguientes aportan una parte cada uno:

Por lo tanto, el número de partes es:

  • nbParts=1+nbEnfants*0,5+(nbEnfants-2)*0,5 si el empleado no está casado;
  • nbParts=2+nbEnfants*0,5+(nbEnfants-2)*0,5 si está casado;
  • donde nbEnfants es el número de hijos que tiene;
  • Se calcula la renta imponible R = 0,9 * S, donde S es el salario anual;
  • Se calcula el cociente familiar QF = R/nbParts;
  • Se calcula el impuesto bruto I a partir de los siguientes datos (2019):
9964
0
0
27 519
0,14
1394,96
73779
0,3
5798
156 244
0,4
13913,69
0
0,45
20163,45

Cada línea tiene 3 campos: campo1, campo2, campo3. Para calcular el impuesto I, se busca la primera línea donde QF<=campo1 y se toman los valores de esa línea. Por ejemplo, para un empleado casado con dos hijos y un salario anual S de 50 000 euros:

Renta imponible: R = 0,9 * S = 45 000

Número de partes: nbParts = 2 + 2 * 0,5 = 3

Cotización familiar: QF = 45 000 / 3 = 15 000

La primera línea en la que QF<=campo1 es la siguiente:

    27 519    0,14    1394,96

El impuesto I es entonces igual a 0,14*R – 1394,96*nbParts=[0,14*45000-1394,96*3]=2115. El impuesto se redondea al euro inferior.

Si la relación QF<=campo1 se cumple desde la primera línea, entonces el impuesto es nulo.

Si QF es tal que la relación QF<=campo1 nunca se cumple, entonces se utilizan los coeficientes de la última línea. Aquí:

    0    0,45    20163,45

lo que da el impuesto bruto I = 0,45*R – 20163,45*nbParts.

4.3.1.2. Límite máximo del coeficiente familiar

 

Para saber si se aplica el límite máximo del coeficiente familiar QF, se vuelve a calcular el impuesto bruto sin tener en cuenta a los hijos. Siguiendo con el ejemplo del asalariado casado con dos hijos y un salario anual S de 50 000 euros:

Renta imponible: R = 0,9 * S = 45 000

Número de partes: nbParts=2 (ya no se cuentan los hijos)

Cotiente familiar: QF = 45 000 / 2 = 22 500

La primera línea en la que QF <= campo1 es la siguiente:

    27519    0,14    1394,96

El impuesto I es entonces igual a 0,14*R – 1394,96*nbParts=[0,14*45000-1394,96*2]=3510.

Ganancia máxima relacionada con los hijos: 1551 * 2 = 3102 euros

Impuesto mínimo: 3510 - 3102 = 408 euros

El impuesto bruto con 2 partes, ya calculado en el párrafo anterior (2115 euros), es superior al impuesto mínimo (408 euros), por lo que el límite máximo familiar no se aplica en este caso.

En general, el impuesto bruto es mayor que (impuesto1, impuesto2), donde:

  • [impuesto1]: es el impuesto bruto calculado con los hijos;
  • [impuesto2]: es el impuesto bruto calculado sin los hijos y reducido en la ganancia máxima (en este caso, 1551 euros por media parte) relacionada con los hijos;

4.3.1.3. Cálculo de la reducción

 

Siguiendo con el ejemplo del asalariado casado con dos hijos y un salario anual S de 50 000 euros:

El impuesto bruto (2115 euros) resultante del paso anterior es inferior a 2627 euros para una pareja (1595 euros para una persona soltera): por lo tanto, se aplica la reducción. Se obtiene mediante el siguiente cálculo:

descuento = umbral (pareja = 1970 / soltero = 1196) - 0,75 * Impuesto bruto

descuento = 1970 - 0,75 * 2115 = 383,75, redondeado a 384 euros.

Nuevo impuesto bruto = 2115 - 384 = 1731 euros

Hay que tener en cuenta dos reglas en el cálculo de la deducción (algunas herramientas de IA han tropezado con esta cuestión):

  • El descuento no puede ser negativo;
  • El descuento no puede ser superior al impuesto ya calculado;

4.3.1.4. Cálculo de la reducción de impuestos

 

Por debajo de un determinado umbral, se aplica una reducción del 20 % sobre el impuesto bruto resultante de los cálculos anteriores. En 2019, los umbrales son los siguientes:

  • soltero: 21 037 euros;
  • pareja: 42 074 euros; (la cifra de 37 968 utilizada en el ejemplo anterior parece errónea);

Este umbral se incrementa en el valor: 3797 * (número de medias partes aportadas por los hijos).

Siguiendo con el ejemplo del asalariado casado con dos hijos y un salario anual S de 50 000 euros:

  • Su renta imponible (45 000 euros) es inferior al umbral (42 074 + 2 × 3797) = 49 668 euros;
  • Por lo tanto, tiene derecho a una reducción del 20 % de su impuesto: 1731 * 0,2 = 346,2 euros, redondeado a 347 euros;
  • El impuesto bruto del contribuyente pasa a ser: 1731 - 347 = 1384 euros; #### 4.3.1.5. *Cálculo del impuesto neto*

Nuestro cálculo se detendrá aquí: el impuesto neto a pagar será de 1384 euros. En la realidad, el contribuyente puede beneficiarse de otras reducciones, en particular por donaciones a organismos de interés público o general.

4.3.1.6. Caso de las rentas altas

Nuestro ejemplo anterior corresponde a la mayoría de los casos de asalariados. Sin embargo, el cálculo del impuesto es diferente en el caso de las rentas altas.

4.3.1.6.1. Límite máximo de la reducción del 10 % sobre los ingresos anuales

En la mayoría de los casos, la renta imponible se obtiene mediante la fórmula: R = 0,9 * S, donde S es el salario anual. A esto se le denomina la reducción del 10 %. Esta reducción tiene un límite máximo. En 2019:

  • No puede ser superior a 12 502 euros;
  • No puede ser inferior a 437 euros;

Tomemos el caso de un asalariado soltero sin hijos y con un salario anual de 200 000 euros:

  • La reducción del 10 % es de 200 000 euros > 12 502 euros. Por lo tanto, se reduce a 12 502 euros; ##### 4.3.1.6.2. Límite máximo del coeficiente familiar

Tomemos un caso en el que se aplica el límite máximo familiar presentado en el apartado |Límite máximo del coeficiente familiar|. Tomemos el caso de una pareja con tres hijos y unos ingresos anuales de 100 000 euros. Repasemos los pasos del cálculo:

  • La deducción del 10 % es de 100 000 euros < 12 502 euros. Por lo tanto, la renta imponible R es de 100 000 - 10 000 = 90 000 euros;
  • La pareja tiene nbParts = 2 + 0,5 × 2 + 1 = 4 partes;
  • Por lo tanto, su coeficiente familiar es QF = R / nbParts = 90 000 / 4 = 22 500 euros;
  • Su impuesto bruto I1 con hijos es I1 = 0,14 × 90 000 – 1394,96 × 4 = 7020 euros;
  • Su impuesto bruto I2 sin hijos:
    • QF = 90 000 / 2 = 45 000 euros;
    • I2 = 0,3 × 90 000 - 5798 × 2 = 15 404 euros;
  • La regla del límite máximo del coeficiente familiar establece que la ganancia aportada por los hijos no puede superar (1551 × 4 medias partes) = 6204 euros. Sin embargo, en este caso, es I2 - I1 = 15 404 - 7020 = 8384 euros, por lo que es superior a 6204 euros;
  • Por lo tanto, el impuesto bruto se recalcula como I3 = I2 - 6204 = 15 404 - 6204 = 9200 euros;
  • Como I3 > I1, se mantendrá el impuesto I3;

Esta pareja no tendrá ni bonificación ni reducción y su impuesto final será de 9200 euros.

4.3.1.7. Cifras oficiales

El cálculo del impuesto es complejo. A lo largo del documento, las pruebas se realizarán con los siguientes ejemplos. Los resultados son los del simulador de la administración tributaria |https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm|:

Contribuyente
Resultados oficiales
Resultados del algoritmo del documento
Pareja con 2 hijos y unos ingresos anuales de 55 555 euros
Impuesto = 2815 euros
Tipo impositivo = 14 %
Impuesto = 2814 euros
Tipo impositivo = 14 %
Pareja con dos hijos y unos ingresos anuales de 50 000 euros
Impuesto = 1385 euros
Descuento = 384 euros
Reducción = 346 euros
Tipo impositivo = 14 %
Impuesto = 1384 euros
Descuento = 384 euros
Descuento = 347 euros
Tipo impositivo = 14 %
Pareja con 3 hijos y unos ingresos anuales de 50 000 euros
Impuesto = 0 euros
Descuento = 720 euros
Descuento = 0 euros
Tipo impositivo = 14 %
Impuesto = 0 euros
Descuento = 720 euros
Descuento = 0 euros
Tipo impositivo = 14 %
Soltero con 2 hijos y unos ingresos anuales de 100 000 euros
Impuesto = 19 884 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 41 %
Impuesto = 19 884 euros
Recargo = 4480 euros
descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 41 %
Soltero con 3 hijos y unos ingresos anuales de 100 000 euros
Impuesto = 16 782 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 41 %
Impuesto = 16 782 euros
Recargo = 7176 euros
Descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 41 %
Pareja con 3 hijos y unos ingresos anuales de 100 000 euros
Impuesto = 9200 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 30 %
Impuesto = 9200 euros
Recargo = 2180 euros
Descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 30 %
Pareja con 5 hijos y unos ingresos anuales de 100 000 euros
Impuesto = 4230 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 14 %
Impuesto = 4230 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 14 %
Soltero sin hijos y con unos ingresos anuales de 100 000 euros
Impuesto = 22 986 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 41 %
Impuesto = 22 986 euros
Recargo = 0 euros
Descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 41 %
Pareja con 2 hijos y unos ingresos anuales de 30 000 euros
Impuesto = 0 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 0 %
Impuesto = 0 euros
Descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 0 %
Soltero sin hijos y con unos ingresos anuales de 200 000 euros
Impuesto = 64 211 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 45 %
Impuesto = 64 210 euros
Recargo = 7498 euros
Descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 45 %
Pareja con 3 hijos y unos ingresos anuales de 200 000 euros
Impuesto = 42 843 euros
Descuento = 0 euros
Descuento = 0 euros
Tipo impositivo = 41 %
Impuesto = 42 842 euros
Recargo = 17 283 euros
descuento = 0 euros
Reducción = 0 euros
Tipo impositivo = 41 %

En el ejemplo anterior, se denomina recargo a lo que pagan de más las rentas altas debido a dos fenómenos:

  • El límite máximo de la deducción del 10 % sobre los ingresos anuales;
  • El límite máximo del coeficiente familiar;

Este indicador no se ha podido verificar porque el simulador de la administración tributaria no lo proporciona.

Se observa que el algoritmo del documento da un impuesto correcto en todos los casos, aunque con un margen de error de 1 euro. Este margen de error se debe a los redondeos. Todas las cantidades de dinero se redondean a veces al euro superior, a veces al euro inferior. Como no conocía las normas oficiales, las cantidades del algoritmo del documento se han redondeado:

  • Al euro superior para los descuentos y rebajas;
  • Al euro inferior para los recargos y el impuesto final;

Vamos a pedirle a la IA que haga este cálculo del impuesto.

4.3.2. Configuración de la sesión de Gemini

La pregunta planteada a Gemini va acompañada de dos archivos:

 
  • En [1], el cálculo que acabamos de detallar se ha incluido en un PDF que se entrega a Gemini. Gemini encontrará en él las reglas exactas para el cálculo simplificado del impuesto de 2019 sobre los ingresos de 2018;
  • En [2], nuestras instrucciones;
  • En [3], para poner en marcha la IA;

Nuestras instrucciones en el archivo de texto [instructionsAvecPDF.txt] son las siguientes:

1 - Exprésate en francés.

2 - ¿Puedes generar un script en Python que permita calcular el impuesto pagado por las familias en 2019 sobre sus ingresos de 2018?

3 - Utiliza el documento PDF que he adjuntado y que explica los cálculos que hay que realizar.

4 - Debes prestar atención a los siguientes puntos:

- Límite máximo del coeficiente familiar. Hay que comprobar los umbrales.
- Cálculo de la reducción en algunos casos. Hay que comprobar los umbrales.
- Cálculo de la reducción del 20 % en algunos casos. Hay que comprobar los umbrales.
- Límite máximo de la deducción del 10 % sobre los ingresos anuales en algunos casos.
- Debes tener en cuenta que todos los ingresos deben declararse por el declarante 1, incluso si la pareja está casada.

5 - Añadirás al script generado pruebas unitarias para los siguientes casos.

En estas pruebas se utiliza:

adultos: número de adultos del núcleo familiar
niños: número de niños del núcleo familiar
ingresos: ingresos netos anuales antes de impuestos, es decir, antes del primer cálculo de la deducción.
impuesto: el impuesto a pagar
descuento: el posible descuento del hogar fiscal
Reducción: la reducción del 20 % para rentas bajas

Estas son las 11 comprobaciones que hay que realizar. Todas ellas se han verificado manualmente en el simulador oficial
del cálculo del impuesto de 2019 [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.html].
Si utilizas este simulador, los ingresos deben asociarse únicamente al declarante 1 en el caso de una pareja, ignorándose entonces al declarante 2. Cuando se reparten los ingresos
entre dos declarantes, no se obtiene el mismo resultado.

Se utiliza la sintaxis (adultos, niños, ingresos) -> (impuesto, descuento, reducción) para indicar que el script recibe las entradas
(adultos, hijos, ingresos) y genera los resultados (impuesto, descuento, reducción)

prueba1: (2,2,55555) -> (2815, 0, 0)
prueba2: (2, 2, 50000) -> (1385, 384, 346)
prueba3: (2,3,50000) -> (0, 720, 0)
prueba4: (1,2,100000) -> (19884, 0, 0)
prueba5: (1,3,100000) -> (16782, 0, 0)
prueba6: (2, 3, 100000) -> (9200, 0, 0)
prueba7: (2, 5, 100000) -> (4230, 0, 0)
prueba8: (1, 0, 100000) -> (22986, 0, 0)
prueba9: (2, 2, 30000) -> (0, 0, 0)
prueba10: (1, 0, 200000) -> (64211, 0, 0)
prueba11: (2, 3, 200000) -> (42843, 0, 0)

6 - Pueden surgir problemas de redondeo. Procederás de la siguiente manera
- el impuesto a pagar se redondeará al euro inferior,
- el descuento se redondeará al euro superior,
- la reducción del 20 % se redondeará al euro superior.
- la deducción del 10 % se redondeará al euro superior

Realiza todas las pruebas unitarias redondeadas al euro más cercano debido a estos posibles errores de redondeo.
No intentes obtener los valores exactos anteriores, sino estos valores redondeados al euro más cercano.

7 - Evita buscar en Internet. El PDF que te proporciono es correcto.
No envíes tu resultado hasta que hayas superado las 11 pruebas unitarias.

8 - Si fallas alguna prueba y te quedas atascado, expón tu razonamiento para esa prueba
para que pueda ayudarte.

9 - Incluye comentarios detallados en el script que generes.
Pon la escala progresiva en una lista o diccionario y luego usa esa lista o diccionario
Pon los números fijos (números mágicos) que utilices en constantes
Utiliza funciones para separar los pasos del cálculo.
Escribe todo en francés

9 - No muestres el código generado en pantalla. Simplemente dame un enlace para descargarlo.
Si las pruebas unitarias fallan, te daré los registros de la ejecución del script para que
puedas ver tus errores.

10 - Si es posible, indica el tiempo en minutos y segundos que has tardado en generar
el script solicitado.

Estas instrucciones son el resultado de numerosas preguntas formuladas a Gemini. Muy pronto nos damos cuenta de que la IA debe estar muy bien guiada si queremos obtener lo que queremos. Es debido a todos estos intentos que la sesión de Gemini fue finalmente interrumpida por exceder los límites. Examinemos el resto de estas instrucciones:

  • Línea 1: se pide que la conversación sea en francés. Esta indicación es para DeepSeek, que tendía a hablar en inglés;
  • Línea 3: lo que queremos;
  • Línea 5: se le dice a la IA que utilice el PDF que se le ha proporcionado;
  • Líneas 7-14: una serie de consejos útiles, sobre todo para el problema 3 sin PDF. Varias IA se han perdido en el cálculo del impuesto;
  • Líneas 15-44: las 11 pruebas unitarias que queremos que se incluyan en el script generado. Cuando se genere el script, lo ejecutaremos en PyCharm y veremos si las 11 pruebas pasan;
  • Líneas 46-53: sin estas instrucciones, las IA generaban pruebas unitarias que buscaban resultados exactos y fallaban;
  • Líneas 55-56: le digo a la IA que no vaya a Internet. La solución más sencilla es utilizar el PDF;
  • Líneas 58-59: la IA no siguió esta instrucción. Me vi obligado a escribirla explícitamente en una pregunta cuando comprobé que una prueba había fallado;
  • Líneas 61-65: indico qué tipo de script de Python deseo;
  • Líneas 67-69: hubiera preferido un enlace para recuperar el script generado, ya que mostrar el código en pantalla lleva tiempo. Resultó que la mayoría de las IA no saben hacer esto. Los enlaces proporcionados no funcionaban;
  • Líneas 71-72: me hubiera gustado saber el tiempo que tardó la IA en responder a la pregunta. Solo Gemini pudo darme esa información. Las demás IA o bien no respondían a esta instrucción, o bien daban cifras fantasiosas que demostraban que no la entendían; ### 4.3.3. La respuesta de Gemini

La primera respuesta de Gemini es la siguiente:

 
  • En [1-4], Gemini proporciona enlaces a la parte del PDF o del archivo de texto de las instrucciones que utiliza en un momento dado;

Lo que sigue es lo siguiente:

 
  • En [1], Gemini afirma que ha ejecutado con éxito las 11 pruebas unitarias. La mayoría de las IA afirmaron lo mismo tanto en el problema 2 como en el problema 3 y, a menudo, al cargar el script generado, este no funcionaba. Por lo tanto, hay que desconfiar de esta afirmación. En el caso de Gemini, resultará ser cierta;
  • En [2], un enlace que resultará no funcionar;
  • En [3], solo Gemini dio un tiempo de ejecución realista;

Así que el enlace [2] no funciona. Se le dice a Gemini:

 

La respuesta de Gemini:

 
  • En [1], el script de Python generado por Gemini;

Cargamos este script en PyCharm y lo ejecutamos:

 
  • En [1], [gemini1] es el script generado por Gemini;

Al ejecutar el script, aparecen errores de compilación:


"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.1/plugins/python-ce/helpers/pycharm/_jb_unittest_runner.py" --path "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py" 
Las pruebas comenzaron a las 17:12 ...
Iniciando pruebas unitarias con los argumentos python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py en C:\Data\st-2025\dev\python\code\python-flask-2025-cours

Traceback (última llamada más reciente):
  Archivo «C:\Program Files\JetBrains\PyCharm 2025.2.0.1\plugins\python-ce\helpers\pycharm\_jb_unittest_runner.py», línea 38, en <módulo>
    sys.exit(main(argv=args, module=None, testRunner=unittestpy.TeamcityTestRunner,
             ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                  buffer=not JB_DISABLE_BUFFERING))
                  ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Archivo «C:\Program Files\Python313\Lib\unittest\main.py», línea 103, en __init__
    self.parseArgs(argv)
    ~~~~~~~~~~~~~~^^^^^^
  Archivo "C:\Program Files\Python313\Lib\unittest\main.py", línea 142, en parseArgs
    self.createTests()
    ~~~~~~~~~~~~~~~~^^
  Archivo «C:\Program Files\Python313\Lib\unittest\main.py», línea 153, en createTests
    self.test = self.testLoader.loadTestsFromNames(self.testNames,
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
                                                   self.module)
                                                   ^^^^^^^^^^^^
  Archivo «C:\Program Files\Python313\Lib\unittest\loader.py», línea 207, en loadTestsFromNames
    suites = [self.loadTestsFromName(name, module) for name in names]
              ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^
  Archivo «C:\Program Files\Python313\Lib\unittest\loader.py», línea 137, en loadTestsFromName
    módulo = __import__(nombre_módulo)
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\chatGPT\chatGPT1.py», línea 28, en <module>
    [cite_start]
     ^^^^^^^^^^
NameError: el nombre 'cite_start' no está definido

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

Secuencia vacía
  • Línea 30, el error de compilación. [cite_start] es un marcador para generar un determinado tipo de texto;

Ponemos los registros anteriores en un archivo [logs gemini1.txt] y se lo pasamos a Gemini:

 

La respuesta de Gemini es entonces la siguiente:

 

Cargado en PyCharm, la ejecución del nuevo script genera exactamente el mismo error. Se le comunica esto a Gemini proporcionándole de nuevo los registros de la ejecución:

 

La respuesta de Gemini es la siguiente:

 

Esta vez sí. Las 11 pruebas unitarias se han superado. Se le comunica a Gemini:

 

Que responde:

 

El script generado por Gemini ha seguido las instrucciones dadas en el archivo de texto [instructionsAvecPDF.txt]:

# -*- coding: utf-8 -*-
"""
Script para el cálculo del impuesto de 2019 sobre los ingresos de 2018 en Francia,
basado en las reglas simplificadas proporcionadas en el documento PDF.
"""

import math
import unittest

# --- CONSTANTES DE CÁLCULO PARA EL IMPUESTO DE 2019 ---
# Todos los valores se han extraído del documento PDF proporcionado.

# Límites máximos para la deducción del 10 % sobre los ingresos anuales
LÍMITE_DEDUCCIÓN_10_POR_CIENTO = 12502.0
MÍNIMO_DEDUCCIÓN_10_POR_CIENTO = 437.0

# Escala del impuesto sobre la renta de 2019
# Estructura: (Umbral máximo del tramo, Tipo impositivo, Importe a deducir por parte)
BAREMA_IMPUESTO_2019 = [
    {"umbral": 9964, "tipo": 0.0, "deducción_por_parte": 0},
    {"umbral": 27519, "tipo": 0.14, "deducción_por_tramo": 1394.96},
    {"umbral": 73779, "tipo": 0.30, "deducción_por_parte": 5798},
    {"umbral": 156244, "tasa": 0.41, "deducción_por_parte": 13913.69},
    {"umbral": float('inf'), "tasa": 0.45, "deducción_por_parte": 20163.45}
]

# Límite máximo del coeficiente familiar
GAIN_MAX_DEMI_PART = 1551.0  # Ganancia máxima por cada media parte adicional

# Umbrales e importes para la reducción
UMBRAL_APLICACIÓN_DESCUENTO = {"soltero": 1595.0, "pareja": 2627.0}
UMBRAL_CÁLCULO_REDUCCIÓN = {"soltero": 1196,0, "pareja": 1970,0}

# Límites para la reducción fiscal del 20 %
UMBRAL_BASE_REDUCCIÓN_20 = {"soltero": 21037,0, "pareja": 42074,0}
INCREMENTO_UMBRAL_REDUCCIÓN_POR_MEDIA_PARTE = 3797.0


def calcular_número_de_partes(adultos: int, niños: int) -> float:
    """
    Calcula el número de partes fiscales del hogar.
    - Cada progenitor aporta 1 parte.
    - Los dos primeros hijos aportan cada uno 1/2 parte.
    - Los siguientes hijos aportan una parte cada uno.
    """
    if adultos not in [1, 2]:
        raise ValueError("El número de adultos debe ser 1 o 2.")

    porciones = float(adultos)
    si niños > 0:
        porciones += 0.5 * min(niños, 2)
    si niños > 2:
        porciones += 1.0 * (niños - 2)

    return partes


def calcular_renta_imponible(ingresos: float) -> float:
    """
    Calcula la renta neta imponible tras la deducción del 10 %.
    Esta deducción tiene un límite máximo.
    La deducción se redondea al euro superior.
    """
    descuento_calculado = ingresos * 0,1
    # La deducción tiene un límite máximo y mínimo
    descuento = min(LÍMITE_MÁXIMO_DESCUENTO_10_POR_CIENTO, max(LÍMITE_MÍNIMO_DESCUENTO_10_POR_CIENTO, descuento_calculado))

    # La pregunta pide redondear la deducción al euro superior
    descuento_redondeado = math.ceil(descuento)

    return ingresos - deducción_redondeada


def calcular_impuesto_bruto_base(ingresos_imponibles: float, número_de_partes: float) -> float:
    """
    Calcula el impuesto bruto según la escala progresiva.
    """
    if número_de_partes <= 0:
        return 0.0

    cociente_familiar = renta_imponible / número_de_partes

    tramo_aplicable = None
    for tramo en BAREMA_IMPUESTOS_2019:
        si cociente_familiar <= tramo["umbral"]:
            tramo_aplicable = tramo
            break

    # Si el QF es superior a todos los umbrales, se utiliza el último tramo
    si tramo_aplicable es None:
        tramo_aplicable = BAREMO_IMPUESTOS_2019[-1]

    # Fórmula para calcular el impuesto bruto
    impuesto_bruto = (ingresos_imponibles * tramo_aplicable["tipo"]) - (
            tramo_aplicable["deducción_por_parte"] * número_de_partes)

    return max(0.0, impuesto_bruto)


def aplicar_límite_qf(impuesto_bruto_con_hijos: float, renta_imponible: float, adultos: int,
                              niños: int) -> float:
    """
    Aplica el límite máximo del coeficiente familiar si es necesario.
    El impuesto final no puede ser inferior al impuesto calculado sin los hijos,
    menos una ganancia máxima por media parte.
    """
    if hijos == 0:
        return impuesto_bruto_con_hijos

    # Cálculo del impuesto sin las partes de los hijos
    partes_sin_hijos = calcular_número_de_partes(adultos, 0)
    impuesto_sin_hijos = calcular_impuesto_bruto_base(ingresos_imponibles, partes_sin_hijos)

    # Cálculo de la ganancia máxima relacionada con los hijos
    cuotas_con_hijos = calcular_número_de_cuotas(adultos, hijos)
    mitad_de_cuotas_hijos = (cuotas_con_hijos - cuotas_sin_hijos) * 2
    gain_maximal = mitad_de_las_partes_por_hijos * GAIN_MAX_DEMI_PART

    impuesto_mínimo_máximo = impuesto_sin_hijos - ganancia_máxima

    # El impuesto retenido es el mayor de los dos cálculos
    return max(impuesto_bruto_con_hijos, impuesto_mínimo_máximo)


def aplicar_descuento(impuesto_antes_del_descuento: float, adultos: int) -> tuple[float, float]:
    """
    Aplica la bonificación si el impuesto es inferior a determinados umbrales.
    La bonificación se redondea al euro superior.
    """
    estado = "pareja" si adultos == 2 else "soltero"
    umbral_aplicación = UMBRAL_APLICACIÓN_DESCUENTO[estado]
    umbral_cálculo = UMBRAL_CÁLCULO_DESCUENTO[estado]

    descuento = 0.0
    # El descuento se aplica si el impuesto está por debajo del umbral de aplicación
    si impuesto_antes_del_descuento < umbral_de_aplicación:
        # Fórmula del descuento: umbral - 0,75 * Impuesto bruto
        descuento_calculado = umbral_calculo - (0,75 * impuesto_antes_del_descuento)

        # Redondeo al euro superior
        descuento = math.ceil(descuento_calculado)

        # El descuento no puede ser negativo ni superior al impuesto
        descuento = max(0.0, min(descuento, impuesto_antes_del_descuento))

    impuesto_después_del_descuento = impuesto_antes_del_descuento - descuento
    return impuesto_después_del_descuento, descuento


def aplicar_reducción_20_por_ciento(impuesto_antes_de_la_reducción: float, renta_imponible: float, adultos: int,
                                    niños: int) -> tupla[float, float]:
    """
    Aplica la reducción del 20 % en función de los ingresos.
    La reducción se redondea al euro superior.
    """
    estado = "pareja" si adultos == 2 else "soltero"
    umbral_base = UMBRAL_BASE_REDUCCIÓN_20[estado]

    # El umbral se incrementa por cada media parte vinculada a los hijos
    partes_con_hijos = calcular_número_de_partes(adultos, hijos)
    partes_adultos = calcular_número_partes(adultos, 0)
    medias_partes_hijos = (partes_con_hijos - partes_adultos) * 2
    umbral_final = umbral_base + (medias_partes_hijos * INCREMENTO_UMBRAL_REDUCCIÓN_POR_MEDIA_PARTE)

    reducción = 0.0
    # La reducción se aplica si la renta imponible está por debajo del umbral
    if renta_imponible < umbral_final:
        reducción_calculada = impuesto_antes_de_la_reducción * 0.20

        # Redondeado al euro superior
        reducción = math.ceil(reducción_calculada)

    impuesto_después_de_la_reducción = impuesto_antes_de_la_reducción - reducción
    return max(0.0, impuesto_después_de_la_reducción), reducción


def calcular_impuesto(adultos: int, ingresos: float, niños: int) -> dict:
    """
    Coordina el cálculo completo del impuesto neto a pagar.
    """
    # Paso 1: Cálculo de la renta imponible
    renta_imponible = calcular_renta_imponible(ingresos)

    # Paso 2: Cálculo del número de cuotas
    número_cuotas = calcular_número_cuotas(adultos, niños)

    # Paso 3: Cálculo del impuesto bruto
    impuesto_bruto_inicial = calcular_impuesto_bruto_base(renta_imponible, número_de_cuotas)

    # Paso 4: Aplicación del límite máximo del QF
    impuesto_después_del_límite = aplicar_límite_qf(impuesto_bruto_inicial, renta_imponible, adultos, niños)

    # Paso 5: Aplicación de la bonificación
    impuesto_después_de_descuento, descuento_calculado = aplicar_descuento(impuesto_después_del_límite, adultos)

    # Paso 6: Aplicación de la reducción del 20 %
    impuesto_antes_del_redondeo_final, reducción_calculada = aplicar_reducción_20_por_ciento(impuesto_después_del_descuento,
                                                                                    renta_imponible, adultos, niños)

    # Paso 7: Redondeo final del impuesto al euro inferior
    impuesto_final = math.floor(impuesto_antes_del_redondeo_final)

    return {
        "impuesto": impuesto_final,
        "descuento": descuento_calculado,
        "reduction": reduction_calculee
    }


# --- PRUEBAS UNITARIAS ---
# Validación del script con los casos proporcionados
class TestCalculImpot2019(unittest.TestCase):

    def _verificar_resultado(self, adultos, niños, ingresos, impuesto_esperado, descuento_esperado, reducción_esperada):
        """Función de utilidad para ejecutar una prueba y verificar los resultados con una precisión de 1 euro."""
        resultado = calcular_impuesto(adultos=adultos, ingresos=ingresos, niños=niños)

        self.assertAlmostEqual(resultado["impuesto"], impuesto_esperado, delta=1,
                               msg=f"Prueba ({adultos}, {niños}, {ingresos}): Impuesto esperado {impuesto_esperado}, obtenido {resultado['impuesto']}")

        self.assertAlmostEqual(resultado["descuento"], descuento_esperado, delta=1,
                               msg=f"Prueba ({adultos}, {niños}, {ingresos}): Descuento esperado {descuento_esperado}, obtenido {resultado['descuento']}")

        self.assertAlmostEqual(resultado["reduction"], reduction_esperada, delta=1,
                               msg=f"Prueba ({adultos}, {niños}, {ingresos}): Descuento esperado {descuento_esperado}, obtenido {resultado['descuento']}")

    def test_cas_1(self):
        # (2, 2, 55555) -> (2815, 0, 0)
        self._verifier_resultado(2, 2, 55555, 2815, 0, 0)

    def test_caso_2(self):
        # (2, 2, 50000) -> (1385, 384, 346)
        self._verifier_resultat(2, 2, 50000, 1385, 384, 346)

    def caso_de_prueba_3(self):
        # (2, 3, 50000) -> (0, 720, 0)
        self._verifier_resultat(2, 3, 50000, 0, 720, 0)

    def test_caso_4(self):
        # (1, 2, 100000) -> (19884, 0, 0)
        self._verifier_resultat(1, 2, 100000, 19884, 0, 0)

    def test_caso_5(self):
        # (1, 3, 100000) -> (16782, 0, 0)
        self._verifier_resultat(1, 3, 100000, 16782, 0, 0)

    def caso_de_prueba_6(self):
        # (2, 3, 100000) -> (9200, 0, 0)
        self._verifier_resultat(2, 3, 100000, 9200, 0, 0)

    def caso_de_prueba_7(self):
        # (2, 5, 100000) -> (4230, 0, 0)
        self._verifier_resultat(2, 5, 100000, 4230, 0, 0)

    def test_caso_8(self):
        # (1, 0, 100000) -> (22986, 0, 0)
        self._verifier_resultat(1, 0, 100000, 22986, 0, 0)

    def caso_de_prueba_9(self):
        # (2, 2, 30000) -> (0, 0, 0)
        self._verifier_resultat(2, 2, 30000, 0, 0, 0)

    def test_cas_10(self):
        # (1, 0, 200000) -> (64211, 0, 0)
        self._verifier_resultat(1, 0, 200000, 64211, 0, 0)

    def test_caso_11(self):
        # (2, 3, 200000) -> (42843, 0, 0)
        self._verifier_resultat(2, 3, 200000, 42843, 0, 0)


if __name__ == '__main__':
    print("Ejecutando pruebas unitarias...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

    # Ejemplo de uso de la calculadora para un caso concreto
    print("\n--- Ejemplo de cálculo ---")
    ingresos_anuales = 50000
    número_de_adultos = 2
    número_de_niños = 2

    resultado_cálculo = calcular_impuesto(adultos=número_adultos, ingresos=ingresos_anuales, hijos=número_hijos)

    print(f"Para una pareja ({nombre_adultos} adultos) con {nombre_niños} hijos y {ingresos_anuales} € de ingresos:")
    print(f"  - Impuesto a pagar: {resultat_calcul['impot']} €")
    print(f"  - Importe de la bonificación: {resultat_calcul['decote']} €")
    print(f"  - Importe de la reducción: {resultat_calcul['reduction']} €")

No he comprobado este código. Dado que las 11 pruebas unitarias han pasado, considero que es «probablemente correcto». No he hecho nada más con mi propio código que comprobar estas 11 pruebas.

4.4. El problema 3

El problema 3 es idéntico al problema 2, salvo que ya no se le da a la IA el PDF que indicaba las reglas de cálculo a seguir.

La pregunta inicial a Gemini es la siguiente:

 

El archivo de instrucciones en [1] es prácticamente el mismo que para el problema 2, con las siguientes diferencias:


1 - Exprésate en francés.

2 - ¿Puedes generar un script en Python que permita calcular los impuestos pagados por las familias en 2019 sobre sus ingresos de 2018?

3 - Utiliza las fuentes que encuentres en Internet. En tu respuesta, indícame dichas fuentes.

4 - Debes prestar atención a los siguientes puntos:

  • En [3], se le pide que busque en Internet las reglas para calcular el impuesto de 2019 sobre los ingresos de 2018. Es un ejercicio más difícil que el anterior;

A continuación solo incluyo partes de la primera respuesta de Gemini:

 
 

El tiempo estimado es plausible. Se tarda mucho en recibir la respuesta de Gemini.

Al igual que antes, Gemini ha proporcionado un enlace de descarga del script generado, enlace que no funciona. Se le indica:

 

La respuesta de Gemini:

 

Cargamos el script en PyCharm con el nombre [gemini2]:

 

Lo ejecutamos y… no funciona. Los registros de la ejecución son los siguientes:


"C:\Program Files\Python313\python.exe" "C:/Program Files/JetBrains/PyCharm 2025.2.0.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\gemini2.py" 
Las pruebas comenzaron a las 17:23 ...
Iniciando pruebas unitarias con los argumentos python -m unittest C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py en C:\Data\st-2025\dev\python\code\python-flask-2025-cours


Fallo
Traceback (última llamada más reciente):
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py», línea 278, en test_cas_2
    self.assertAlmostEqual(impot, 1385, delta=1)
    ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
AssertionError: 1691 != 1385 dentro de 1 delta (306 de diferencia)


Error
Traceback (última llamada más reciente):
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py», línea 291, en test_cas_3
    impot, _, _ = calculer_impot_final(2, 3, 50000)
                  ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^
  Archivo "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", línea 187, en calculer_impot_final
    descuento, impuesto_después_del_descuento = calcular_descuento(impuesto_después_del_límite_máximo, adultos)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py», línea 134, en calculer_decote
    descuento = umbral_descuento - (impuesto_antes_del_descuento * COEFICIENTE_DESCUENTO)
                                                  ^^^^^^^^^^^^^^^^^
NameError: el nombre «COFFICIENT_DECOTE» no está definido. ¿Querías decir: «COEFFICIENT_DECOTE»?


Error
Traceback (última llamada más reciente):
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py», línea 316, en test_cas_9
    self._verifier_cas(2, 2, 30000, (0, 0, 0))
    ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
  Archivo "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", línea 216, en _verifier_cas
    impuesto_calculado, descuento_calculado, reducción_calculada = calcular_impuesto_final(adultos, niños, ingresos)
                                                         ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Archivo "C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py", línea 187, en calculer_impot_final
    descuento, impuesto_después_del_descuento = calcular_descuento(impuesto_después_del_límite_máximo, adultos)
                                 ~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  Archivo «C:\Data\st-2025\dev\python\code\python-flask-2025-cours\outils ia\gemini\gemini2.py», línea 134, en calculer_decote
    descuento = umbral_descuento - (impuesto_antes_del_descuento * COEFICIENTE_DESCUENTO)
                                                  ^^^^^^^^^^^^^^^^^
NameError: el nombre «COFFICIENT_DECOTE» no está definido. ¿Querías decir: «COEFFICIENT_DECOTE»?



Se han ejecutado 11 pruebas en 0,038 s

FALLO (fallos=1, errores=2)

El proceso ha finalizado con el código de salida 1
  • Línea 11, una prueba ha fallado;
  • Líneas 25 y 42: el mismo error de compilación;

Guardamos estos registros en un archivo de texto que le pasamos a Gemini:

 

La respuesta de Gemini:

 
 

Cargamos el script en PyCharm y lo ejecutamos. Más errores. Se lo comunicamos a Gemini adjuntando de nuevo los registros de la ejecución:

 

La respuesta de Gemini:

 
 
 

Cargamos este nuevo script en PyCharm y lo ejecutamos. Esta vez pasan las 11 pruebas unitarias:

El código generado por Gemini es el siguiente:

# -*- coding: utf-8 -*-
import math
import unittest

# ==============================================================================
# CONSTANTES PARA EL IMPUESTO SOBRE LA RENTA DE 2018 (A PAGAR EN 2019)
# ==============================================================================
BAREMA_IMPUESTO_2019 = [
    {'umbral': 0, 'tipo': 0.0},
    {'umbral': 9964, 'tipo': 0.14},
    {'umbral': 27519, 'tipo': 0.30},
    {'umbral': 73779, 'tipo': 0.41},
    {'umbral': 156244, 'tipo': 0.45},
]
DEDUCCIÓN_FIJA_TIPO = 0,10
DEDUCCIÓN_FIJA_MÍNIMA = 437
DEDUCCIÓN_FIJA_MÁXIMA = 12502
LÍMITE_MÁXIMO_VENTAJA_QF_MEDIA_PARTE = 1551
UMBRAL_DE_DESCUENTO_SOLTERO = 1196
UMBRAL_DE_REDUCCIÓN_PAREJA = 1970
COEFICIENTE_DE_DESCUENTO = 0,75
TIPO_REDUCCIÓN_20_POR_CIENTO = 0,20
UMBRAL_RFR_REDUCCIÓN_SOLTERO = 18985
UMBRAL_RFR_REDUCCIÓN_PAREJA = 37970
INCREMENTO_RFR_REDUCCIÓN_POR_MEDIA_PARTE = 3803


# ==============================================================================
# FUNCIONES DE CÁLCULO
# ==============================================================================

def calcular_número_de_partes(adultos: int, niños: int) -> float:
    """Calcula el número de partes del núcleo familiar."""
    if adultos < 1: return 0
    porciones = float(adultos)
    si niños >= 1: partes += 0.5
    si niños >= 2: partes += 0.5
    si niños >= 3: partes += (niños - 2) * 1.0
    return partes


def calcular_renta_imponible(ingresos_brutos: float) -> float:
    """Calcula la renta neta imponible tras una deducción del 10 %."""
    si ingresos_brutos == 0: devuelve 0.0
    descuento = ingresos_brutos * DESCUENTO_FIJO_TIPO
    si deducción < DEDUCCIÓN_FIJA_MÍNIMA: deducción = DEDUCCIÓN_FIJA_MÍNIMA
    if deducción > LÍMITE_MÁXIMO_DEDUCCIÓN_FIJA: deducción = LÍMITE_MÁXIMO_DEDUCCIÓN_FIJA
    descuento = math.ceil(descuento)
    renta_imponible = ingresos_brutos - deducción
    return renta_imponible if renta_imponible > 0 else 0


def calcular_impuesto_bruto_por_tramo(renta_imponible: float, número_de_partes: float) -> float:
    """Calcula el impuesto bruto aplicando la escala progresiva."""
    if número_de_partes <= 0 or renta_imponible <= 0: return 0.0
    cociente_familiar = renta_imponible / número_de_partes
    impuesto_por_parte = 0.0
    for i in range(len(BAREMA_IMPUESTOS_2019)):
        tramo = BAREMA_IMPUESTOS_2019[i]
        umbral_actual = tramo['umbral']
        tipo = tramo['tipo']
        si cociente_familiar > umbral_actual:
            base_imposable_tramo = 0
            si i < len(BAREMA_IMPUESTOS_2019) - 1:
                umbral_siguiente = BAREMO_IMPUESTOS_2019[i + 1]['umbral']
                base_imposable_tramo = min(cuota_familiar, umbral_siguiente) - umbral_actual
            else:
                base_imposable_tramo = coeficiente_familiar - umbral_actual
            impuesto_por_parte += base_imponible_tramo * tipo
    return impuesto_por_parte * número_de_partes


def aplicar_límite_qf(ingresos_imponibles: float, número_de_partes: float, adultos: int,
                              impuesto_bruto_familia: float) -> float:
    """Aplica el límite máximo a los efectos del coeficiente familiar."""
    if número_de_partes <= adultos: return impuesto_bruto_familiar
    impuesto_base = calcular_impuesto_bruto_por_tramo(ingresos_imponibles, float(adultos))
    mitad_de_partes_hijos = (número_de_partes - adultos) * 2
    ventaja_qf_máx = LÍMITE_VENTAJA_QF_MEDIA_PARTE * medias_partes_hijos
    ventaja_qf_real = impuesto_base - impuesto_bruto_familiar
    si ventaja_qf_real > ventaja_qf_máx:
        return impuesto_base - ventaja_qf_máx
    devuelve impuesto_bruto_familia


def calcular_impuesto_final(adultos: int, hijos: int, ingresos: float) -> (int, int, int):
    """Coordina el cálculo completo del impuesto."""
    ingresos_imponibles = calcular_ingresos_imponibles(ingresos)
    número_cuotas = calcular_número_cuotas(adultos, niños)
    impuesto_bruto = calcular_impuesto_bruto_por_tramo(ingresos_imponibles, número_de_cuotas)
    impuesto_después_del_límite = aplicar_límite_qf(ingresos_imponibles, número_de_cuotas, adultos, impuesto_bruto)

    # CORRECCIÓN: Si el impuesto ya es 0, no hay descuento ni reducción.
    if impuesto_después_del_límite == 0:
        return 0, 0, 0

    # --- LÓGICA CORREGIDA PARA LA INTERACCIÓN DESCUENTO / REDUCCIÓN ---
    descuento_calculado = 0.0
    reducción_calculada = 0.0
    impuesto_a_tratar = impuesto_después_del_límite

    # 1. Determinar la elegibilidad para la reducción del 20 %
    es_elegible_para_la_reducción = False
    si adultos >= 2:
        umbral_rfr = UMBRAL_RFR_REDUCCIÓN_PAREJA + max(0, (número_partes - 2) * 2) * INCREMENTO_RFR_REDUCCIÓN_POR_MEDIA_PARTE
    else:
        umbral_rfr = UMBRAL_RFR_REDUCCIÓN_SOLTEROS + max(0, (
                número_partes - 1) * 2) * INCREMENTO_RFR_REDUCCIÓN_POR_MEDIA_PARTE
    si renta_imponible < umbral_rfr:
        es_elegible_para_la_reducción = True

    # 2. Determinar la elegibilidad para la bonificación
    umbral_descuento_hogar = UMBRAL_DESCUENTO_PAREJA si adultos >= 2 else UMBRAL_DESCUENTO_SOLTERO
    reducción_teórica = math.ceil(impuesto_a_calcular * TIPO_REDUCCIÓN_20_POR_CIENTO) si es_elegible_para_la_reducción; de lo contrario, 0

    si (impuesto_a_tratar - reducción_teórica) < umbral_descuento_hogar:
        descuento_calculado = umbral_descuento_hogar - (impuesto_a_tratar * COEFICIENTE_DESCUENTO)
        descuento_calculado = math.ceil(descuento_calculado)
        # CORRECCIÓN: La bonificación devuelta no puede superar el impuesto que anula
        descuento_calculado = min(descuento_calculado, math.ceil(impuesto_a_tratar))
        impuesto_a_tratar -= descuento_calculado
        impuesto_a_tratar = max(0.0, impuesto_a_tratar)

    # 3. Aplicar la reducción del 20 % si es aplicable
    if es_elegible_para_la_reducción:
        reducción_calculada = math.ceil(impuesto_a_tratar * PORCENTAJE_REDUCCIÓN_20%)
        impuesto_a_pagar -= reducción_calculada
        impuesto_a_pagar = max(0.0, impuesto_a_pagar)

    impuesto_final = math.floor(impuesto_a_calcular)

    return impuesto_final, int(descuento_calculado), int(reducción_calculada)


# ==============================================================================
# PRUEBAS UNITARIAS
# ==============================================================================

class TestCalculImpot2019(unittest.TestCase):
    def _verificar_caso(self, adultos, niños, ingresos, esperado):
        impuesto_esperado, descuento_esperado, reducción_esperada = esperado
        impuesto_calculado, descuento_calculado, reducción_calculada = calcular_impuesto_final(adultos, niños, ingresos)
        self.assertAlmostEqual(impuesto_calculado, impuesto_esperado, delta=1, msg=f"Impuesto ({impuesto_calculado} frente a {impuesto_esperado})")
        self.assertAlmostEqual(descuento_calculado, descuento_esperado, delta=1,
                               msg=f"Descuento ({descuento_calculado} vs {descuento_esperado})")
        self.assertAlmostEqual(reduction_calculada, reduction_esperada, delta=1,
                               msg=f"Descuento ({descuento_calculado} vs {descuento_esperado})")

    def test_caso_1(self): self._verificar_caso(2, 2, 55555, (2815, 0, 0))

    def test_caso_2(self): self._verificar_caso(2, 2, 50000, (1385, 384, 346))

    def test_caso_3(self): self._verificador_de_casos(2, 3, 50000, (0, 720, 0))

    def test_cas_4(self): self._verifier_cas(1, 2, 100000, (19884, 0, 0))

    def test_cas_5(self): self._verifier_cas(1, 3, 100000, (16782, 0, 0))

    def test_cas_6(self): self._verifier_cas(2, 3, 100000, (9200, 0, 0))

    def test_cas_7(self): self._verifier_cas(2, 5, 100000, (4230, 0, 0))

    def test_cas_8(self): self._verifier_cas(1, 0, 100000, (22986, 0, 0))

    def test_cas_9(self): self._verifier_cas(2, 2, 30000, (0, 0, 0))

    def test_cas_10(self): self._verifier_cas(1, 0, 200000, (64211, 0, 0))

    def test_cas_11(self): self._verifier_cas(2, 3, 200000, (42843, 0, 0))


if __name__ == '__main__':
    print("Iniciando pruebas unitarias para el cálculo del impuesto de 2019...")
    unittest.main(argv=['first-arg-is-ignored'], exit=False)

Una vez más, no he revisado este código. Simplemente he comprobado que superaba las 11 pruebas con éxito.

Pero puede que nos interese conocer su razonamiento, especialmente en los casos particulares del cálculo del impuesto. Se lo preguntamos:

 

Se trata de un caso de altos ingresos con un posible límite máximo de la deducción del 10 % y un posible límite máximo del coeficiente familiar.

La respuesta de Gemini es la siguiente:

 
 
 
 
 

Estas dos últimas capturas de pantalla son interesantes. Gemini utiliza un método de cálculo diferente al explicado en el PDF. De hecho, este método de cálculo se puede encontrar en Internet. Ambos métodos son equivalentes.

 
 
 

La explicación destaca por su claridad. Se podría dar tal cual a los estudiantes para explicar el método de cálculo del impuesto.

Ahora tomemos otro ejemplo, esta vez con ingresos bajos. En este caso, puede haber una bonificación y una reducción:

 

La respuesta de Gemini es la siguiente:

 
 
 
 
 

Aquí se observa que Gemini aplica una regla que no figura en el PDF. Probablemente la haya encontrado en Internet, pero ¿es fiable la fuente?

 

Aquí, Gemini sigue aplicando una regla desconocida (la regla especial mencionada anteriormente).

 
 
 

Así pues, los resultados de Gemini coinciden con el resultado del simulador oficial de impuestos. Pero ha utilizado una regla que no figura en el PDF. ¿Dónde está el error? Se lo preguntamos adjuntándole el PDF:

 

La respuesta de Gemini:

 
 
 
 

Creo que Gemini tiene razón y que mi PDF es erróneo. Para verificarlo, le pido que haga una prueba:

  • En la que su razonamiento daría los mismos resultados que el simulador oficial de impuestos;
  • Donde el razonamiento del PDF le daría resultados diferentes a los del simulador;
 

La respuesta de Gemini es la siguiente:

 

Aquí Gemini se equivoca. He ejecutado el simulador con este ejemplo y he encontrado lo siguiente:

 

Sin embargo, vamos a descubrir que el razonamiento de Gemini da efectivamente los resultados anteriores. Continuamos:

 
 
 
 

Muy bien. Tomamos nota. Continuamos:

 
 
 
 

Así pues, Gemini ha obtenido (impuesto, descuento, reducción) = (431, 325, 1296), mientras que el simulador que he utilizado yo da (431, 324, 1297). Por lo tanto, Gemini ha obtenido los resultados correctos con una diferencia de 1 euro, pero no lo sabe. Se lo decimos:

 

Gemini responde:

 
 

Ahora nos preguntamos si Gemini podría generar un PDF corregido:

 

La respuesta de Gemini:

 

Así pues, Gemini no me ha proporcionado un enlace a un PDF, pero ha generado un texto para que yo mismo genere ese PDF. Aunque resulte pesado incluir aquí las capturas de pantalla del PDF, lo hago para que el lector vea el aspecto generativo de la IA:

 

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Image

Sinceramente, no he comprobado si todo lo que se dice en este PDF es cierto. En cualquier caso, es un documento perfecto para un trabajo práctico, que se genera en unos segundos.

No obstante, podemos hacer que el propio Gemini compruebe que su PDF es correcto. Iniciamos una nueva conversación:

 
  • en [1], hemos puesto el PDF generado por Gemini [El problema según Gemini.pdf];
  • en [2], [instructionsAvecPDF2.txt] es idéntico a las instrucciones de [instructionsAvecPDF.txt], salvo que se ha añadido una duodécima prueba unitaria, la misma que demostró que el PDF inicial era erróneo:
test12: (2, 2, 49500) -> (1297, 431, 324)

Curiosamente, fueron necesarias varias idas y venidas antes de que Gemini generara el script correcto:

Pregunta 2

 

Pregunta 3

 

Como ya se ha hecho varias veces, cuando el script generado y cargado en PyCharm falla, le damos a Gemini el archivo de texto con los registros de la ejecución. Gemini los entiende muy bien.

Pregunta 4

 

Pregunta 5

 

Pregunta 6 y fin

Ahora estamos seguros de la validez del PDF generado por Gemini. Las reglas de cálculo que contiene son correctas.

Ahora haremos lo mismo con las otras cinco IA, pero seremos muy breves en nuestras explicaciones, salvo en el caso de ChatGPT, la IA líder en este momento. Lo que nos interesa es saber si la IA resuelve o no los tres problemas que le planteamos. De hecho, las interfaces de todas estas IA son muy similares y he procedido con ellas igual que con Gemini. Se anima al lector a reproducir las conversaciones de Gemini con la IA de su elección.