
Quiero DEMOCRATIZAR el conocimiento del cálculo del IRPF y del salario neto, y las implicaciones de la progresividad en frío entre 2012 y 2026. Pero necesito fiscalistas y economistas con ojo al detalle y techies. Quiero que los que sabéis de fiscalidad auditéis los resultados de mi código para cada año atendiendo al más mínimo detalle (yo he tratado de hacerlo), Que los techies me propongáis mejoras en el código y optimizaciones en las salidas, y quiero que, finalmente, los que tenéis capacidad de montar una web con ello, os coordinéis y lo hagáis. También quiero que extraigáis un manual sencillo para que la gente lo entienda a partir de los cálculos que se hacen en el código. Y que se expliquen las distintas normativas y cómo impactaron. A TRABAJAR. GO GO GO! @Gsnchez @XMihura @Inspectores_IHE @frdelatorre @Jaume_Vinas @SantiCalvo_Eco Ahí va el código! import pandas as pd import numpy as np # ============================================================================= # 1. MÓDULO MACROECONÓMICO: INFLACIÓN ACUMULADA (DICIEMBRE A DICIEMBRE) # ============================================================================= IPC_ANUAL_DIC = { 2013: 0.003, 2014: -0.010, 2015: 0.000, 2016: 0.016, 2017: 0.011, 2018: 0.012, 2019: 0.008, 2020: -0.005, 2021: 0.065, 2022: 0.057, 2023: 0.031, 2024: 0.028, 2025: 0.029, 2026: 0.030 } def obtener_inflacion_acumulada(anio_base, anio_destino=2026): if anio_base == anio_destino: return 1.0 multiplicador = 1.0 for anio in range(anio_base + 1, anio_destino + 1): multiplicador *= (1 + IPC_ANUAL_DIC[anio]) return multiplicador INFLACION_A_2026 = {anio: obtener_inflacion_acumulada(anio, 2026) for anio in range(2012, 2027)} # ============================================================================= # 2. NORMATIVA FISCAL Y LABORAL (IRPF Y SS) # ============================================================================= def obtener_parametros(anio): p = {} # Bases y Tipos Generales SS p['base_max'] = { 2012: 39150.0, 2013: 41108.4, 2014: 43164.0, 2015: 43272.0, 2016: 43704.0, 2017: 45014.4, 2018: 45014.4, 2019: 48841.2, 2020: 48841.2, 2021: 48841.2, 2022: 49672.8, 2023: 53946.0, 2024: 56646.0, 2025: 58914.0, 2026: 61214.4 }[anio] p['ss_tipos'] = { 'comunes': [0.236, 0.047], 'desempleo': [0.055, 0.0155], 'fogasa': [0.002, 0.0], 'fp': [0.006, 0.001], 'atep': [0.015, 0.0] } # MEI y Solidaridad if anio == 2023: p['mei'] = [0.005, 0.001] elif anio == 2024: p['mei'] = [0.0058, 0.0012] elif anio == 2025: p['mei'] = [0.0067, 0.0013] elif anio >= 2026: p['mei'] = [0.0075, 0.0015] else: p['mei'] = [0.0, 0.0] if anio == 2025: p['solidaridad'] = [(1.10, 0.0092), (1.50, 0.0100), (float('inf'), 0.0117)] elif anio >= 2026: p['solidaridad'] = [(1.10, 0.0115), (1.50, 0.0125), (float('inf'), 0.0146)] else: p['solidaridad'] = [] # Mínimos y Gastos p['irpf_minimo'] = 5151 if anio <= 2014 else 5550 p['minimo_exento'] = { 2012: 11162, 2013: 11162, 2014: 11162, 2015: 12000, 2016: 12000, 2017: 12000, 2018: 12643, 2019: 14000, 2020: 14000, 2021: 14000, 2022: 14000, 2023: 15000, 2024: 15876, 2025: 15876, 2026: 15876 }[anio] p['gastos_fijos'] = 0 if anio <= 2014 else 2000 # Reducción Art 20 (y Metadatos para control) def get_art20_params(a): if a <= 2014: return {"U_Inf": 9180, "R_Max": 4080, "U_Sup": 13260, "R_Min": 2652} elif 2015 <= a <= 2017: return {"U_Inf": 11250, "R_Max": 3700, "U_Sup": 14450, "R_Min": 0} elif a == 2018: return {"U_Inf": "Transitorio", "R_Max": "Transitorio", "U_Sup": "Transitorio", "R_Min": "Transitorio"} elif 2019 <= a <= 2022: return {"U_Inf": 13115, "R_Max": 5565, "U_Sup": 16825, "R_Min": 0} elif a == 2023: return {"U_Inf": 14047.5, "R_Max": 6498, "U_Sup": 19747.5, "R_Min": 0} else: return {"U_Inf": 14852, "R_Max": 7302, "U_Sup": 19747.5, "R_Min": 0} p['art20_meta'] = get_art20_params(anio) def reduccion_trabajo(rn_previo): if anio <= 2014: if rn_previo <= 9180: return 4080.0 elif rn_previo <= 13260: return 4080.0 - 0.35 * (rn_previo - 9180.0) else: return 2652.0 elif 2015 <= anio <= 2017: if rn_previo <= 11250: return 3700.0 elif rn_previo <= 14450: return 3700.0 - 1.15625 * (rn_previo - 11250.0) else: return 0.0 elif anio == 2018: # Régimen Transitorio pre = 3700.0 if rn_previo <= 11250 else (3700.0 - 1.15625 * (rn_previo - 11250.0) if rn_previo <= 14450 else 0.0) post = 5565.0 if rn_previo <= 13115 else (max(0.0, 5565.0 - 1.5 * (rn_previo - 13115.0)) if rn_previo <= 16825 else 0.0) return (pre / 2.0) + (post / 2.0) elif 2019 <= anio <= 2022: if rn_previo <= 13115: return 5565.0 elif rn_previo <= 16825: return max(0.0, 5565.0 - 1.5 * (rn_previo - 13115.0)) else: return 0.0 elif anio == 2023: if rn_previo <= 14047.50: return 6498.0 elif rn_previo <= 19747.50: return max(0.0, 6498.0 - 1.14 * (rn_previo - 14047.50)) else: return 0.0 elif anio >= 2024: if rn_previo <= 14852: return 7302.0 elif rn_previo <= 17673.52: return 7302.0 - 1.75 * (rn_previo - 14852.0) elif rn_previo <= 19747.50: return 2364.34 - 1.14 * (rn_previo - 17673.52) else: return 0.0 return 0.0 p['reduccion_trabajo'] = reduccion_trabajo # Escalas IRPF if anio <= 2014: p['tramos_irpf'] = [(17707, 0.2475), (33007, 0.30), (53407, 0.40), (120000, 0.47), (175000, 0.49), (300000, 0.51), (float('inf'), 0.52)] elif anio == 2015: p['tramos_irpf'] = [(12450, 0.195), (20200, 0.245), (34000, 0.305), (60000, 0.38), (float('inf'), 0.46)] elif 2016 <= anio <= 2020: p['tramos_irpf'] = [(12450, 0.19), (20200, 0.24), (35200, 0.30), (60000, 0.37), (float('inf'), 0.45)] else: p['tramos_irpf'] = [(12450, 0.19), (20200, 0.24), (35200, 0.30), (60000, 0.37), (300000, 0.45), (float('inf'), 0.47)] # Deducción SMI def deduccion_smi(bruto): if anio == 2026: if bruto <= 17094: return 590.89 else: return max(0.0, 590.89 - 0.20 * (bruto - 17094.0)) elif anio == 2025: if bruto <= 16576: return 340.0 elif bruto <= 18276: return max(0, 340.0 - 0.20 * (bruto - 16576.0)) return 0.0 p['deduccion_smi'] = deduccion_smi return p # ============================================================================= # 3. GENERACIÓN DE HOJAS DE CONTROL DE PARÁMETROS # ============================================================================= def generar_hojas_control(): general = [] tramos_lista = [] for anio in range(2012, 2027): p = obtener_parametros(anio) tipo_emp = sum(x[0] for x in p['ss_tipos'].values()) tipo_tra = sum(x[1] for x in p['ss_tipos'].values()) general.append({ "Año": anio, "Base Máx. Anual": p['base_max'], "SS Empleador %": round(tipo_emp * 100, 2), "SS Empleado %": round(tipo_tra * 100, 2), "MEI Empleador %": round(p['mei'][0] * 100, 3), "MEI Empleado %": round(p['mei'][1] * 100, 3), "Gastos Fijos Art.19": p['gastos_fijos'], "Mín. Contribuyente": p['irpf_minimo'], "Mín. Exento Retención": p['minimo_exento'], "Art.20 Umbral Inf": p['art20_meta']['U_Inf'], "Art.20 Red. Máxima": p['art20_meta']['R_Max'], "Art.20 Umbral Sup": p['art20_meta']['U_Sup'], "Art.20 Red. Mínima": p['art20_meta']['R_Min'] }) for i, (lim, tip) in enumerate(p['tramos_irpf']): tramos_lista.append({ "Año": anio, "Nº Tramo": i + 1, "Hasta Base": lim if lim != float('inf') else "En adelante", "Tipo %": round(tip * 100, 2) }) return pd.DataFrame(general), pd.DataFrame(tramos_lista) # ============================================================================= # 4. MOTOR DETALLADO (PARA PESTAÑAS ANUALES DAT_YYYY) # ============================================================================= def calcular_cuotas_por_tramo(base_liq, tramos): cuotas_tramos = {f"T{i+1} ({round(tipo*100, 1)}%)": 0.0 for i, (_, tipo) in enumerate(tramos)} cuota_total = 0.0 if base_liq <= 0: return cuotas_tramos, cuota_total lim_ant = 0.0 for i, (lim, tipo) in enumerate(tramos): nombre = f"T{i+1} ({round(tipo*100, 1)}%)" if base_liq > lim: cuota = (lim - lim_ant) * tipo cuotas_tramos[nombre] = cuota cuota_total += cuota lim_ant = lim else: cuota = (base_liq - lim_ant) * tipo cuotas_tramos[nombre] = cuota cuota_total += cuota break return cuotas_tramos, cuota_total def procesar_ano(anio): p = obtener_parametros(anio) # Rango exhaustivo: 0 a 100.000€ de 1€ en 1€ salarios_brutos = np.arange(0, 100001, 1) resultados = [] for bruto in salarios_brutos: base_cotizacion = min(bruto, p['base_max']) exceso_base = max(0, bruto - p['base_max']) tipo_empresa = sum(x[0] for x in p['ss_tipos'].values()) + p['mei'][0] tipo_trabajador = sum(x[1] for x in p['ss_tipos'].values()) + p['mei'][1] cot_empresa = base_cotizacion * tipo_empresa cot_trabajador = base_cotizacion * tipo_trabajador if p['solidaridad'] and exceso_base > 0: tramo1_limite = p['base_max'] * 0.10 tramo2_limite = p['base_max'] * 0.50 exceso1 = min(exceso_base, tramo1_limite) exceso2 = min(max(0, exceso_base - tramo1_limite), tramo2_limite - tramo1_limite) exceso3 = max(0, exceso_base - tramo2_limite) cuota_sol_total = (exceso1 * p['solidaridad'][0][1]) + (exceso2 * p['solidaridad'][1][1]) + (exceso3 * p['solidaridad'][2][1]) cot_empresa += cuota_sol_total * (5/6) cot_trabajador += cuota_sol_total * (1/6) coste_laboral = bruto + cot_empresa rendimiento_previo_sin_fijos = bruto - cot_trabajador red_trabajo = p['reduccion_trabajo'](rendimiento_previo_sin_fijos) rendimiento_neto = max(0, rendimiento_previo_sin_fijos - p['gastos_fijos']) base_imponible = max(0, rendimiento_neto - red_trabajo) cuotas_tramos, cuota_integra = calcular_cuotas_por_tramo(base_imponible, p['tramos_irpf']) cuota_minimo = p['irpf_minimo'] * p['tramos_irpf'][0][1] cuota_teorica = max(0, cuota_integra - cuota_minimo) deduccion = p['deduccion_smi'](bruto) cuota_con_deduccion = max(0, cuota_teorica - deduccion) limite_retencion = max(0, (bruto - p['minimo_exento']) * 0.43) irpf_final = min(cuota_con_deduccion, limite_retencion) salario_neto = bruto - cot_trabajador - irpf_final fila = { "Salario Bruto": bruto, "Cot. Soc. Empresa": round(cot_empresa, 2), "Coste Laboral": round(coste_laboral, 2), "Cot. Soc. Trab.": round(cot_trabajador, 2), "Ren. Previo": round(rendimiento_previo_sin_fijos, 2), "Gastos Fijos": p['gastos_fijos'], "Red. Ren. Trab.": round(red_trabajo, 2), "Base Imponible": round(base_imponible, 2) } for k, v in cuotas_tramos.items(): fila[k] = round(v, 2) fila.update({ "Cuota Íntegra": round(cuota_integra, 2), "Cuota Mínimo Personal": round(cuota_minimo, 2), "Cuota Teórica": round(cuota_teorica, 2), "Deducción SMI": round(deduccion, 2), "Cuota tras SMI": round(cuota_con_deduccion, 2), "Límite 43% (Art 85.3)": round(limite_retencion, 2), "IRPF Final": round(irpf_final, 2), "Salario Neto": round(salario_neto, 2) }) resultados.append(fila) return pd.DataFrame(resultados) # ============================================================================= # 5. MOTOR RÁPIDO PARA COMPARATIVA INFLACIÓN # ============================================================================= def calcular_nomina_agregada(bruto, anio, p): base_cot = min(bruto, p['base_max']) exc_base = max(0, bruto - p['base_max']) t_emp = sum(x[0] for x in p['ss_tipos'].values()) + p['mei'][0] t_tra = sum(x[1] for x in p['ss_tipos'].values()) + p['mei'][1] cot_emp = base_cot * t_emp cot_tra = base_cot * t_tra if p['solidaridad'] and exc_base > 0: l1, l2 = p['base_max']*0.1, p['base_max']*0.5 e1, e2, e3 = min(exc_base, l1), min(max(0, exc_base-l1), l2-l1), max(0, exc_base-l2) q_sol = (e1*p['solidaridad'][0][1]) + (e2*p['solidaridad'][1][1]) + (e3*p['solidaridad'][2][1]) cot_emp += q_sol * (5/6); cot_tra += q_sol * (1/6) coste_lab = bruto + cot_emp rn_previo = bruto - cot_tra red20 = p['reduccion_trabajo'](rn_previo) base_imp = max(0, rn_previo - p['gastos_fijos'] - red20) q_integra = 0.0 lim_ant = 0.0 for lim, tipo in p['tramos_irpf']: if base_imp > lim: q_integra += (lim - lim_ant) * tipo lim_ant = lim else: q_integra += (base_imp - lim_ant) * tipo break q_min = p['irpf_minimo'] * p['tramos_irpf'][0][1] q_teorica = max(0, q_integra - q_min) q_smi = max(0, q_teorica - p['deduccion_smi'](bruto)) lim_ret = max(0, (bruto - p['minimo_exento']) * 0.43) irpf_final = min(q_smi, lim_ret) return coste_lab, cot_emp, cot_tra, irpf_final, bruto - cot_tra - irpf_final def generar_comparativa_inflacion(): # Análisis comparativo en saltos de 1.000€ salarios_2026 = np.arange(15000, 100001, 1000) p_2026 = obtener_parametros(2026) ref_2026 = {b: calcular_nomina_agregada(b, 2026, p_2026) for b in salarios_2026} resultados = [] for anio in range(2012, 2027): p_anio = obtener_parametros(anio) inf_acum = INFLACION_A_2026[anio] for bruto_26 in salarios_2026: bruto_nom = bruto_26 / inf_acum c_lab_n, c_emp_n, c_tra_n, irpf_n, neto_n = calcular_nomina_agregada(bruto_nom, anio, p_anio) c_lab_aj = c_lab_n * inf_acum c_emp_aj = c_emp_n * inf_acum c_tra_aj = c_tra_n * inf_acum irpf_aj = irpf_n * inf_acum neto_aj = neto_n * inf_acum neto_2026_real = ref_2026[bruto_26][4] dif_poder_adq = neto_aj - neto_2026_real resultados.append({ "Año a Comparar": anio, "Salario Equivalente (2026)": bruto_26, "Multiplicador IPC Acum.": round(inf_acum, 4), "IPC Acumulado (%)": f"{round((inf_acum - 1)*100, 2)}%", "Salario Bruto Nominal": round(bruto_nom, 2), "Coste Lab. (Euros 2026)": round(c_lab_aj, 2), "SS Emp. (Euros 2026)": round(c_emp_aj, 2), "SS Tra. (Euros 2026)": round(c_tra_aj, 2), "IRPF (Euros 2026)": round(irpf_aj, 2), "Neto Real en su Año": round(neto_aj, 2), "Neto Real en 2026": round(neto_2026_real, 2), "Variación Poder Adquisitivo Mensual vs 2026 (12 pagas)": round(dif_poder_adq / 12, 2), "Pérdida/Ganancia Anual Poder Adq.": round(dif_poder_adq, 2) }) return pd.DataFrame(resultados) # ============================================================================= # 6. EJECUCIÓN MAESTRA Y GENERACIÓN DEL EXCEL COMPLETO # ============================================================================= nombre_fichero = 'Auditoria_Integral_Nominas_e_Inflacion_2012_2026.xlsx' print("Iniciando la creación del mega-archivo Excel. ¡Paciencia, puede tardar un par de minutos!...") with pd.ExcelWriter(nombre_fichero, engine='openpyxl') as writer: # 1. Pestañas de Parámetros de Control print("Generando hojas de control normativo...") df_gen, df_tra = generar_hojas_control() df_gen.to_excel(writer, sheet_name='CONTROL_GENERAL', index=False) df_tra.to_excel(writer, sheet_name='CONTROL_TRAMOS_IRPF', index=False) # 2. Pestaña Comparativa Inflación print("Calculando y generando comparativa ajustada por IPC...") df_comparativa = generar_comparativa_inflacion() df_comparativa.to_excel(writer, sheet_name='COMPARATIVA_INFLACION', index=False) # 3. Pestañas Anuales Detalladas (de 1€ en 1€) for anio in range(2012, 2027): print(f"Calculando nóminas detalladas para el año {anio} (100.001 registros)...") df_ano = procesar_ano(anio) df_ano.to_excel(writer, sheet_name=f'DAT_{anio}', index=False) print(f"\n¡Éxito total! Archivo '{nombre_fichero}' creado correctamente con todas las auditorías solicitadas.")


















