{#- SPDX-License-Identifier: Apache-2.0 SPDX-FileCopyrightText: 2025 - 2026 BMO Soluciones, S.A. -#} {% extends "base.html" %} {% macro fmt_money(value) -%} {%- if value is not none and value != '' -%} {{ "{:,.2f}".format(value|float) }} {%- else -%} N/A {%- endif -%} {%- endmacro %} {% macro fmt_pct(value) -%} {%- if value is not none and value != '' -%} {{ "{:,.2f}".format(value|float) }}% {%- else -%} N/A {%- endif -%} {%- endmacro %} {% macro fmt_int(value) -%} {%- if value is not none and value != '' -%} {{ "{:,}".format(value|int) }} {%- else -%} N/A {%- endif -%} {%- endmacro %} {% block content %} {% set resumen = comparacion_payload.get('resumen', {}) if comparacion_payload else {} %} {% set distribucion = resumen.get('distribucion', {}) if resumen else {} %} {% set alertas = comparacion_payload.get('alertas', {}) if comparacion_payload else {} %} {% set rojas = alertas.get('rojas', {}) if alertas else {} %} {% set amarillas = alertas.get('amarillas', {}) if alertas else {} %} {% set impacto = comparacion_payload.get('impacto_empleados', {}) if comparacion_payload else {} %} {% set concentracion = comparacion_payload.get('concentracion_impacto', {}) if comparacion_payload else {} %} {% set ratios = comparacion_payload.get('ratios', {}) if comparacion_payload else {} %} {% set flujo = comparacion_payload.get('flujo_caja', {}) if comparacion_payload else {} %} {% set calidad = comparacion_payload.get('calidad', {}) if comparacion_payload else {} %} {% set estructurales = comparacion_payload.get('cambios_estructurales', {}) if comparacion_payload else {} %} {% set segmentacion = comparacion_payload.get('segmentacion', {}) if comparacion_payload else {} %} {% set seg_departamentos = segmentacion.get('departamentos', []) if segmentacion else [] %} {% set seg_contrato = segmentacion.get('tipo_contrato', []) if segmentacion else [] %} {% set bucket_variacion = comparacion_payload.get('bucket_variacion_neto', []) if comparacion_payload else [] %} {% set indice = comparacion_payload.get('indice_estabilidad', {}) if comparacion_payload else {} %} {% set empleados = comparacion_payload.get('empleados', {}) if comparacion_payload else {} %} {% set conceptos = comparacion_payload.get('conceptos', {}) if comparacion_payload else {} %} {% set radar_top = conceptos.get('radar_top', []) if conceptos else [] %} {% set componentes = comparacion_payload.get('componentes_planilla', {}) if comparacion_payload else {} %} {% set vacaciones = comparacion_payload.get('vacaciones', {}) if comparacion_payload else {} %} {% set reglas_vacaciones = vacaciones.get('reglas', []) if vacaciones else [] %}

Comparar Nóminas

Volver a la nómina
Seleccionar base de comparación
{% if nominas_para_comparar %}
{% else %}
Sin datos
{% endif %}
{% if comparacion_payload %}
{% if comparacion_payload.get('is_cached') %} Cache {% else %} Recalculado ahora {% endif %} {% if comparacion_payload.get('cache_generado_en') %} N/A {% else %} N/A {% endif %} {% set nivel_estabilidad = indice.get('nivel') %} Índice de estabilidad: {{ indice.get('score', 'N/A') }} ({{ nivel_estabilidad or 'N/A' }})
{% if comparacion_payload.get('es_calculo_actual') is sameas false %} {% endif %} {% if comparacion_payload.get('planilla_actual_aprobada') %} {% set flujo_aprobacion = comparacion_payload.get('flujo_aprobacion', {}) %} {% endif %}

Alertas rojas

{{ fmt_int(alertas.get('total_rojas')) }}

Alertas amarillas

{{ fmt_int(alertas.get('total_amarillas')) }}

Neto base → actual
{{ fmt_money(resumen.get('total_neto_base')) }} → {{ fmt_money(resumen.get('total_neto_actual')) }}
Δ {{ fmt_money(resumen.get('variacion_total_neto')) }} ({{ fmt_pct(resumen.get('variacion_total_neto_pct')) }})
Deducciones base → actual
{{ fmt_money(resumen.get('total_deducciones_base')) }} → {{ fmt_money(resumen.get('total_deducciones_actual')) }}
Δ {{ fmt_money(resumen.get('variacion_total_deducciones')) }} ({{ fmt_pct(resumen.get('variacion_total_deducciones_pct')) }})
Var dias calculo
{{ fmt_int(resumen.get('dias_calculo_base')) }} -> {{ fmt_int(resumen.get('dias_calculo_actual')) }}
{% set var_dias = resumen.get('variacion_dias_calculo') %} Delta {% if var_dias is not none and var_dias > 0 %}+{% endif %}{{ fmt_int(var_dias) }}

% empleados con variación
{{ fmt_pct(impacto.get('porcentaje_con_variacion')) }}
{{ fmt_int(impacto.get('empleados_con_variacion')) }} de {{ fmt_int(impacto.get('total_comunes')) }}
Concentración top 10 empleados
{{ fmt_pct(concentracion.get('top_10_empleados_pct')) }}
Concentración top 10 conceptos
{{ fmt_pct(concentracion.get('top_10_conceptos_pct')) }}
Bucket variación neto
    {% if bucket_variacion %} {% for bucket in bucket_variacion %} {% set total_comunes = impacto.get('total_comunes')|int %} {% set bucket_cantidad = bucket.cantidad|int %} {% set pct_bucket = ((bucket_cantidad / total_comunes * 100) if total_comunes > 0 else none) %}
  • {{ bucket.rango }} {{ fmt_int(bucket_cantidad) }}{% if pct_bucket is not none %} ({{ fmt_pct(pct_bucket) }}){% endif %}
  • {% endfor %} {% else %}
  • Sin datos
  • {% endif %}

Std Neto (base/actual)
{{ fmt_money(distribucion.get('std_neto_base')) }} / {{ fmt_money(distribucion.get('std_neto_actual')) }}
IQR Neto (base/actual)
{{ fmt_money(distribucion.get('iqr_neto_base')) }} / {{ fmt_money(distribucion.get('iqr_neto_actual')) }}
Ratio Deducciones/Bruto
{{ fmt_pct(ratios.get('ratio_deducciones_bruto_base')) }} → {{ fmt_pct(ratios.get('ratio_deducciones_bruto_actual')) }}
Cambio estructural
{% if estructurales.get('reglas_cambiadas') or estructurales.get('catalogos_cambiados') or estructurales.get('tipos_cambio_modificados') %}Sí{% else %}No{% endif %}
Detalle cambios estructurales
Reglas {% if estructurales.get('reglas_cambiadas') %}❌{% else %}✅{% endif %} Catálogos {% if estructurales.get('catalogos_cambiados') %}❌{% else %}✅{% endif %} Tipo de cambio {% if estructurales.get('tipos_cambio_modificados') %}❌{% else %}✅{% endif %}
Segmentación por departamento
{% for item in seg_departamentos[:10] %} {% else %}{% endfor %}
Departamento Empleados Δ Neto
{{ item.departamento or 'Sin datos' }} {{ fmt_int(item.empleados) }} {{ fmt_money(item.variacion_total_neto) }} {{ fmt_pct(item.variacion_pct) }}
Sin datos
Segmentación por tipo contrato
{% for item in seg_contrato[:10] %} {% else %}{% endfor %}
Tipo Empleados Δ Neto
{{ item.tipo or 'Sin datos' }} {{ fmt_int(item.empleados) }} {{ fmt_money(item.variacion_total_neto) }} {{ fmt_pct(item.variacion_pct) }}
Sin datos
Calidad y consistencia
  • Empleados con novedades (base){{ fmt_int(calidad.get('empleados_con_novedades_base')) }}
  • Empleados con novedades (actual){{ fmt_int(calidad.get('empleados_con_novedades_actual')) }}
  • % actual con novedades{{ fmt_pct(calidad.get('porcentaje_actual')) }}
Impacto en flujo de caja: Δ Neto {{ fmt_money(flujo.get('variacion_total_neto')) }}, Δ Deducciones {{ fmt_money(flujo.get('variacion_total_deducciones')) }}, Δ Bruto {{ fmt_money(flujo.get('variacion_total_bruto')) }}

{% set solo_base = empleados.get('solo_en_base', []) %}
Solo en nómina base ({{ solo_base|length }})
    {% for item in solo_base[:50] %}
  • {{ item.empleado_codigo }} - {{ item.nombre }}
  • {% else %}
  • Sin datos
  • {% endfor %}
{% if solo_base|length > 50 %}
+{{ solo_base|length - 50 }} más
{% endif %}
{% set solo_actual = empleados.get('solo_en_actual', []) %}
Solo en nómina actual ({{ solo_actual|length }})
    {% for item in solo_actual[:50] %}
  • {{ item.empleado_codigo }} - {{ item.nombre }}
  • {% else %}
  • Sin datos
  • {% endfor %}
{% if solo_actual|length > 50 %}
+{{ solo_actual|length - 50 }} más
{% endif %}
Top outliers por Δ neto
{% for item in empleados.get('outliers_neto', []) %} {% else %} {% endfor %}
Empleado Neto base Neto actual Δ Driver principal Severidad
{{ item.empleado_codigo }} - {{ item.nombre }} {{ fmt_money(item.neto_base) }} {{ fmt_money(item.neto_actual) }} {{ fmt_money(item.variacion_neto) }} {{ fmt_pct(item.variacion_neto_pct) }} {{ item.driver or 'Sin datos' }} {{ item.severidad or 'N/A' }}
Sin datos

Percepciones configuradas: {{ componentes.get('percepciones', [])|join(', ') if componentes.get('percepciones') else 'Sin datos' }}

Deducciones configuradas: {{ componentes.get('deducciones', [])|join(', ') if componentes.get('deducciones') else 'Sin datos' }}

Prestaciones configuradas: {{ componentes.get('prestaciones', [])|join(', ') if componentes.get('prestaciones') else 'Sin datos' }}

Reglas de cálculo: {{ componentes.get('reglas_calculo', [])|join(', ') if componentes.get('reglas_calculo') else 'Sin datos' }}

Top 10 conceptos por impacto monetario
{% for item in radar_top %} {% else %} {% endfor %}
Tipo Código Monto base Monto actual Δ # Emp. base # Emp. actual
{{ item.tipo }} {{ item.codigo }} {{ fmt_money(item.monto_base) }} {{ fmt_money(item.monto_actual) }} {{ fmt_money(item.variacion) }} {{ fmt_pct(item.variacion_pct) }} {{ fmt_int(item.empleados_base) }} {{ fmt_int(item.empleados_actual) }}
Sin datos

Alertas de riesgo
  • Empleados con neto negativo: {{ fmt_int(rojas.get('neto_negativo')) }}
  • Empleados con neto cero: {{ fmt_int(rojas.get('neto_cero')) }}
  • Outliers de neto: {{ fmt_int(amarillas.get('outliers_neto')) }}
  • Cambios de salario base: {{ fmt_int(amarillas.get('salario_base_cambiado')) }}
Vacaciones

Reglas de vacaciones comparadas: {{ fmt_int(vacaciones.get('total_reglas')) }}

    {% for regla in reglas_vacaciones %}
  • {{ regla.codigo_concepto }}: {{ fmt_money(regla.cantidad_base) }} → {{ fmt_money(regla.cantidad_actual) }} (Δ {{ fmt_money(regla.variacion) }})
  • {% else %}
  • Sin datos
  • {% endfor %}
{% endif %} {% endblock %}