Charlie
2.3K posts

Charlie
@TradeTheBeat
Trading and techno, mostly
Barcelona, España Katılım Ekim 2021
679 Takip Edilen218 Takipçiler
Charlie retweetledi

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.")
Español

🔴 #ÚLTIMAHORA Illa confirma que el catalán será requisito para renovar el permiso de residencia
abc.es/espana/catalun…
Español


Greenpeace afirma que España y Portugal pueden lograr emisiones 0 en 2040 sin nuclear si bajan un 39% su demanda energética
f.mtr.cool/ynukgryxyn
Español

Charlie retweetledi
Charlie retweetledi

First I made the Sierra Chart handbook free, now I'm making this detailed video tutorial free as well. It's for @SierraChart users.
I don't plan on making more videos, but wanted to make the video Free. Enjoy, and share with someone new to Sierra Chart.
youtu.be/cBwzi5-kO1I

YouTube
English

@denboraldia lol, esto ya lo he leído otras veces. El mayor logro de la vida laborall del funcionario: aprobar un puto examen
Español

El 98% de personas que trabajan en lo privado, no pasarían un proceso de selección de lo público.
MANDANGA CON V DE VENGANZA@manmandangon
el 98% de los empleados públicos no pasarían el mes de prueba en lo privado
Español
Charlie retweetledi
Charlie retweetledi
Charlie retweetledi

🚨The most disturbing physics demonstration in existence involves nothing more than two pendulums and a pin.
Here's why:
There's no electricity, or quantum effects or exotic materials or laboratory conditions. Just two rods, a joint connecting them, and gravity. Every force acting on this system is completely understood. The equations describing its motion were written centuries ago. Nothing about it is mysterious at the level of physics.
And yet predicting its motion beyond a few seconds is physically impossible.
A difference of 0.000001 degrees in the starting angle doesn't produce a small deviation downstream. It produces a completely unrelated trajectory. The system doesn't drift gradually from the prediction. It departs so violently that within moments the prediction becomes pure fiction.
Most people misunderstand chaos theory because the word chaos implies randomness. The double pendulum contains zero randomness. Every swing is fully governed by deterministic laws. A being with perfect knowledge of every starting condition could calculate every future position exactly. The problem is that perfect knowledge cannot exist in physical reality. Every measurement humans make carries some tolerance, however microscopic. That margin gets amplified exponentially each second until the gap between prediction and reality swallows everything whole.
The universe runs on math that outruns our ability to feed it accurate inputs.
What should genuinely disturb you is that the double pendulum is not a special case. The same sensitivity lives inside weather systems, economies, neural firing patterns, and ecosystems. Every complex system you depend on operates under identical conditions. Tiny upstream differences explode into massive downstream divergence with no warning and no recovery.
Philosophers spent centuries arguing about whether the future is predetermined. That was always the wrong argument. The double pendulum settled the only question that actually matters in daily life.
Determined and predictable are not the same thing. They never were.
Interesting things@awkwardgoogle
The unpredictability of the double pendulum.
English




















