coati-payroll 0.0.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of coati-payroll might be problematic. Click here for more details.

Files changed (243) hide show
  1. coati_payroll/__init__.py +415 -0
  2. coati_payroll/app.py +95 -0
  3. coati_payroll/audit_helpers.py +904 -0
  4. coati_payroll/auth.py +123 -0
  5. coati_payroll/cli.py +1318 -0
  6. coati_payroll/config.py +219 -0
  7. coati_payroll/demo_data.py +813 -0
  8. coati_payroll/enums.py +278 -0
  9. coati_payroll/forms.py +1769 -0
  10. coati_payroll/formula_engine/__init__.py +81 -0
  11. coati_payroll/formula_engine/ast/__init__.py +110 -0
  12. coati_payroll/formula_engine/ast/ast_visitor.py +259 -0
  13. coati_payroll/formula_engine/ast/expression_evaluator.py +228 -0
  14. coati_payroll/formula_engine/ast/safe_operators.py +131 -0
  15. coati_payroll/formula_engine/ast/type_converter.py +172 -0
  16. coati_payroll/formula_engine/data_sources.py +752 -0
  17. coati_payroll/formula_engine/engine.py +247 -0
  18. coati_payroll/formula_engine/exceptions.py +52 -0
  19. coati_payroll/formula_engine/execution/__init__.py +24 -0
  20. coati_payroll/formula_engine/execution/execution_context.py +52 -0
  21. coati_payroll/formula_engine/execution/step_executor.py +62 -0
  22. coati_payroll/formula_engine/execution/variable_store.py +59 -0
  23. coati_payroll/formula_engine/novelty_codes.py +206 -0
  24. coati_payroll/formula_engine/results/__init__.py +20 -0
  25. coati_payroll/formula_engine/results/execution_result.py +59 -0
  26. coati_payroll/formula_engine/steps/__init__.py +30 -0
  27. coati_payroll/formula_engine/steps/assignment_step.py +71 -0
  28. coati_payroll/formula_engine/steps/base_step.py +48 -0
  29. coati_payroll/formula_engine/steps/calculation_step.py +42 -0
  30. coati_payroll/formula_engine/steps/conditional_step.py +122 -0
  31. coati_payroll/formula_engine/steps/step_factory.py +58 -0
  32. coati_payroll/formula_engine/steps/tax_lookup_step.py +45 -0
  33. coati_payroll/formula_engine/tables/__init__.py +24 -0
  34. coati_payroll/formula_engine/tables/bracket_calculator.py +51 -0
  35. coati_payroll/formula_engine/tables/table_lookup.py +161 -0
  36. coati_payroll/formula_engine/tables/tax_table.py +32 -0
  37. coati_payroll/formula_engine/validation/__init__.py +24 -0
  38. coati_payroll/formula_engine/validation/schema_validator.py +37 -0
  39. coati_payroll/formula_engine/validation/security_validator.py +52 -0
  40. coati_payroll/formula_engine/validation/tax_table_validator.py +205 -0
  41. coati_payroll/formula_engine_examples.py +153 -0
  42. coati_payroll/i18n.py +54 -0
  43. coati_payroll/initial_data.py +613 -0
  44. coati_payroll/interes_engine.py +450 -0
  45. coati_payroll/liquidacion_engine/__init__.py +25 -0
  46. coati_payroll/liquidacion_engine/engine.py +267 -0
  47. coati_payroll/locale_config.py +165 -0
  48. coati_payroll/log.py +138 -0
  49. coati_payroll/model.py +2410 -0
  50. coati_payroll/nomina_engine/__init__.py +87 -0
  51. coati_payroll/nomina_engine/calculators/__init__.py +30 -0
  52. coati_payroll/nomina_engine/calculators/benefit_calculator.py +79 -0
  53. coati_payroll/nomina_engine/calculators/concept_calculator.py +254 -0
  54. coati_payroll/nomina_engine/calculators/deduction_calculator.py +105 -0
  55. coati_payroll/nomina_engine/calculators/exchange_rate_calculator.py +51 -0
  56. coati_payroll/nomina_engine/calculators/perception_calculator.py +75 -0
  57. coati_payroll/nomina_engine/calculators/salary_calculator.py +86 -0
  58. coati_payroll/nomina_engine/domain/__init__.py +27 -0
  59. coati_payroll/nomina_engine/domain/calculation_items.py +52 -0
  60. coati_payroll/nomina_engine/domain/employee_calculation.py +53 -0
  61. coati_payroll/nomina_engine/domain/payroll_context.py +44 -0
  62. coati_payroll/nomina_engine/engine.py +188 -0
  63. coati_payroll/nomina_engine/processors/__init__.py +28 -0
  64. coati_payroll/nomina_engine/processors/accounting_processor.py +171 -0
  65. coati_payroll/nomina_engine/processors/accumulation_processor.py +90 -0
  66. coati_payroll/nomina_engine/processors/loan_processor.py +227 -0
  67. coati_payroll/nomina_engine/processors/novelty_processor.py +42 -0
  68. coati_payroll/nomina_engine/processors/vacation_processor.py +67 -0
  69. coati_payroll/nomina_engine/repositories/__init__.py +32 -0
  70. coati_payroll/nomina_engine/repositories/acumulado_repository.py +83 -0
  71. coati_payroll/nomina_engine/repositories/base_repository.py +40 -0
  72. coati_payroll/nomina_engine/repositories/config_repository.py +102 -0
  73. coati_payroll/nomina_engine/repositories/employee_repository.py +34 -0
  74. coati_payroll/nomina_engine/repositories/exchange_rate_repository.py +58 -0
  75. coati_payroll/nomina_engine/repositories/novelty_repository.py +54 -0
  76. coati_payroll/nomina_engine/repositories/planilla_repository.py +52 -0
  77. coati_payroll/nomina_engine/results/__init__.py +24 -0
  78. coati_payroll/nomina_engine/results/error_result.py +28 -0
  79. coati_payroll/nomina_engine/results/payroll_result.py +53 -0
  80. coati_payroll/nomina_engine/results/validation_result.py +39 -0
  81. coati_payroll/nomina_engine/services/__init__.py +22 -0
  82. coati_payroll/nomina_engine/services/accounting_voucher_service.py +708 -0
  83. coati_payroll/nomina_engine/services/employee_processing_service.py +173 -0
  84. coati_payroll/nomina_engine/services/payroll_execution_service.py +374 -0
  85. coati_payroll/nomina_engine/services/snapshot_service.py +295 -0
  86. coati_payroll/nomina_engine/validators/__init__.py +31 -0
  87. coati_payroll/nomina_engine/validators/base_validator.py +48 -0
  88. coati_payroll/nomina_engine/validators/currency_validator.py +50 -0
  89. coati_payroll/nomina_engine/validators/employee_validator.py +87 -0
  90. coati_payroll/nomina_engine/validators/period_validator.py +44 -0
  91. coati_payroll/nomina_engine/validators/planilla_validator.py +136 -0
  92. coati_payroll/plugin_manager.py +176 -0
  93. coati_payroll/queue/__init__.py +33 -0
  94. coati_payroll/queue/driver.py +127 -0
  95. coati_payroll/queue/drivers/__init__.py +22 -0
  96. coati_payroll/queue/drivers/dramatiq_driver.py +268 -0
  97. coati_payroll/queue/drivers/huey_driver.py +390 -0
  98. coati_payroll/queue/drivers/noop_driver.py +54 -0
  99. coati_payroll/queue/selector.py +121 -0
  100. coati_payroll/queue/tasks.py +764 -0
  101. coati_payroll/rate_limiting.py +83 -0
  102. coati_payroll/rbac.py +183 -0
  103. coati_payroll/report_engine.py +512 -0
  104. coati_payroll/report_export.py +208 -0
  105. coati_payroll/schema_validator.py +167 -0
  106. coati_payroll/security.py +77 -0
  107. coati_payroll/static/styles.css +1044 -0
  108. coati_payroll/system_reports.py +573 -0
  109. coati_payroll/templates/auth/login.html +189 -0
  110. coati_payroll/templates/base.html +283 -0
  111. coati_payroll/templates/index.html +227 -0
  112. coati_payroll/templates/macros.html +146 -0
  113. coati_payroll/templates/modules/calculation_rule/form.html +78 -0
  114. coati_payroll/templates/modules/calculation_rule/index.html +102 -0
  115. coati_payroll/templates/modules/calculation_rule/schema_editor.html +1159 -0
  116. coati_payroll/templates/modules/carga_inicial_prestacion/form.html +170 -0
  117. coati_payroll/templates/modules/carga_inicial_prestacion/index.html +170 -0
  118. coati_payroll/templates/modules/carga_inicial_prestacion/reporte.html +193 -0
  119. coati_payroll/templates/modules/config_calculos/index.html +44 -0
  120. coati_payroll/templates/modules/configuracion/index.html +90 -0
  121. coati_payroll/templates/modules/currency/form.html +47 -0
  122. coati_payroll/templates/modules/currency/index.html +64 -0
  123. coati_payroll/templates/modules/custom_field/form.html +62 -0
  124. coati_payroll/templates/modules/custom_field/index.html +78 -0
  125. coati_payroll/templates/modules/deduccion/form.html +1 -0
  126. coati_payroll/templates/modules/deduccion/index.html +1 -0
  127. coati_payroll/templates/modules/employee/form.html +254 -0
  128. coati_payroll/templates/modules/employee/index.html +76 -0
  129. coati_payroll/templates/modules/empresa/form.html +74 -0
  130. coati_payroll/templates/modules/empresa/index.html +71 -0
  131. coati_payroll/templates/modules/exchange_rate/form.html +47 -0
  132. coati_payroll/templates/modules/exchange_rate/import.html +93 -0
  133. coati_payroll/templates/modules/exchange_rate/index.html +114 -0
  134. coati_payroll/templates/modules/liquidacion/index.html +58 -0
  135. coati_payroll/templates/modules/liquidacion/nueva.html +51 -0
  136. coati_payroll/templates/modules/liquidacion/ver.html +91 -0
  137. coati_payroll/templates/modules/payroll_concepts/audit_log.html +146 -0
  138. coati_payroll/templates/modules/percepcion/form.html +1 -0
  139. coati_payroll/templates/modules/percepcion/index.html +1 -0
  140. coati_payroll/templates/modules/planilla/config.html +190 -0
  141. coati_payroll/templates/modules/planilla/config_deducciones.html +129 -0
  142. coati_payroll/templates/modules/planilla/config_empleados.html +116 -0
  143. coati_payroll/templates/modules/planilla/config_percepciones.html +113 -0
  144. coati_payroll/templates/modules/planilla/config_prestaciones.html +118 -0
  145. coati_payroll/templates/modules/planilla/config_reglas.html +120 -0
  146. coati_payroll/templates/modules/planilla/ejecutar_nomina.html +106 -0
  147. coati_payroll/templates/modules/planilla/form.html +197 -0
  148. coati_payroll/templates/modules/planilla/index.html +144 -0
  149. coati_payroll/templates/modules/planilla/listar_nominas.html +91 -0
  150. coati_payroll/templates/modules/planilla/log_nomina.html +135 -0
  151. coati_payroll/templates/modules/planilla/novedades/form.html +177 -0
  152. coati_payroll/templates/modules/planilla/novedades/index.html +170 -0
  153. coati_payroll/templates/modules/planilla/ver_nomina.html +477 -0
  154. coati_payroll/templates/modules/planilla/ver_nomina_empleado.html +231 -0
  155. coati_payroll/templates/modules/plugins/index.html +71 -0
  156. coati_payroll/templates/modules/prestacion/form.html +1 -0
  157. coati_payroll/templates/modules/prestacion/index.html +1 -0
  158. coati_payroll/templates/modules/prestacion_management/dashboard.html +150 -0
  159. coati_payroll/templates/modules/prestacion_management/initial_balance_bulk.html +195 -0
  160. coati_payroll/templates/modules/prestamo/approve.html +156 -0
  161. coati_payroll/templates/modules/prestamo/condonacion.html +249 -0
  162. coati_payroll/templates/modules/prestamo/detail.html +443 -0
  163. coati_payroll/templates/modules/prestamo/form.html +203 -0
  164. coati_payroll/templates/modules/prestamo/index.html +150 -0
  165. coati_payroll/templates/modules/prestamo/pago_extraordinario.html +211 -0
  166. coati_payroll/templates/modules/prestamo/tabla_pago_pdf.html +181 -0
  167. coati_payroll/templates/modules/report/admin_index.html +125 -0
  168. coati_payroll/templates/modules/report/detail.html +129 -0
  169. coati_payroll/templates/modules/report/execute.html +266 -0
  170. coati_payroll/templates/modules/report/index.html +95 -0
  171. coati_payroll/templates/modules/report/permissions.html +64 -0
  172. coati_payroll/templates/modules/settings/index.html +274 -0
  173. coati_payroll/templates/modules/shared/concept_form.html +201 -0
  174. coati_payroll/templates/modules/shared/concept_index.html +145 -0
  175. coati_payroll/templates/modules/tipo_planilla/form.html +70 -0
  176. coati_payroll/templates/modules/tipo_planilla/index.html +68 -0
  177. coati_payroll/templates/modules/user/form.html +65 -0
  178. coati_payroll/templates/modules/user/index.html +76 -0
  179. coati_payroll/templates/modules/user/profile.html +81 -0
  180. coati_payroll/templates/modules/vacation/account_detail.html +149 -0
  181. coati_payroll/templates/modules/vacation/account_form.html +52 -0
  182. coati_payroll/templates/modules/vacation/account_index.html +68 -0
  183. coati_payroll/templates/modules/vacation/dashboard.html +156 -0
  184. coati_payroll/templates/modules/vacation/initial_balance_bulk.html +149 -0
  185. coati_payroll/templates/modules/vacation/initial_balance_form.html +93 -0
  186. coati_payroll/templates/modules/vacation/leave_request_detail.html +158 -0
  187. coati_payroll/templates/modules/vacation/leave_request_form.html +61 -0
  188. coati_payroll/templates/modules/vacation/leave_request_index.html +98 -0
  189. coati_payroll/templates/modules/vacation/policy_detail.html +176 -0
  190. coati_payroll/templates/modules/vacation/policy_form.html +152 -0
  191. coati_payroll/templates/modules/vacation/policy_index.html +79 -0
  192. coati_payroll/templates/modules/vacation/register_taken_form.html +178 -0
  193. coati_payroll/translations/en/LC_MESSAGES/messages.mo +0 -0
  194. coati_payroll/translations/en/LC_MESSAGES/messages.po +7283 -0
  195. coati_payroll/translations/es/LC_MESSAGES/messages.mo +0 -0
  196. coati_payroll/translations/es/LC_MESSAGES/messages.po +7374 -0
  197. coati_payroll/vacation_service.py +451 -0
  198. coati_payroll/version.py +18 -0
  199. coati_payroll/vistas/__init__.py +64 -0
  200. coati_payroll/vistas/calculation_rule.py +307 -0
  201. coati_payroll/vistas/carga_inicial_prestacion.py +423 -0
  202. coati_payroll/vistas/config_calculos.py +72 -0
  203. coati_payroll/vistas/configuracion.py +87 -0
  204. coati_payroll/vistas/constants.py +17 -0
  205. coati_payroll/vistas/currency.py +112 -0
  206. coati_payroll/vistas/custom_field.py +120 -0
  207. coati_payroll/vistas/employee.py +305 -0
  208. coati_payroll/vistas/empresa.py +153 -0
  209. coati_payroll/vistas/exchange_rate.py +341 -0
  210. coati_payroll/vistas/liquidacion.py +205 -0
  211. coati_payroll/vistas/payroll_concepts.py +580 -0
  212. coati_payroll/vistas/planilla/__init__.py +38 -0
  213. coati_payroll/vistas/planilla/association_routes.py +238 -0
  214. coati_payroll/vistas/planilla/config_routes.py +158 -0
  215. coati_payroll/vistas/planilla/export_routes.py +175 -0
  216. coati_payroll/vistas/planilla/helpers/__init__.py +34 -0
  217. coati_payroll/vistas/planilla/helpers/association_helpers.py +161 -0
  218. coati_payroll/vistas/planilla/helpers/excel_helpers.py +29 -0
  219. coati_payroll/vistas/planilla/helpers/form_helpers.py +97 -0
  220. coati_payroll/vistas/planilla/nomina_routes.py +488 -0
  221. coati_payroll/vistas/planilla/novedad_routes.py +227 -0
  222. coati_payroll/vistas/planilla/routes.py +145 -0
  223. coati_payroll/vistas/planilla/services/__init__.py +26 -0
  224. coati_payroll/vistas/planilla/services/export_service.py +687 -0
  225. coati_payroll/vistas/planilla/services/nomina_service.py +233 -0
  226. coati_payroll/vistas/planilla/services/novedad_service.py +126 -0
  227. coati_payroll/vistas/planilla/services/planilla_service.py +34 -0
  228. coati_payroll/vistas/planilla/validators/__init__.py +18 -0
  229. coati_payroll/vistas/planilla/validators/planilla_validators.py +40 -0
  230. coati_payroll/vistas/plugins.py +45 -0
  231. coati_payroll/vistas/prestacion.py +272 -0
  232. coati_payroll/vistas/prestamo.py +808 -0
  233. coati_payroll/vistas/report.py +432 -0
  234. coati_payroll/vistas/settings.py +29 -0
  235. coati_payroll/vistas/tipo_planilla.py +134 -0
  236. coati_payroll/vistas/user.py +172 -0
  237. coati_payroll/vistas/vacation.py +1045 -0
  238. coati_payroll-0.0.2.dist-info/LICENSE +201 -0
  239. coati_payroll-0.0.2.dist-info/METADATA +581 -0
  240. coati_payroll-0.0.2.dist-info/RECORD +243 -0
  241. coati_payroll-0.0.2.dist-info/WHEEL +5 -0
  242. coati_payroll-0.0.2.dist-info/entry_points.txt +2 -0
  243. coati_payroll-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,477 @@
1
+ {% extends "base.html" %}
2
+ {% block content %}
3
+ <div class="content-header">
4
+ <h3>
5
+ Nómina: {{ nomina.periodo_inicio.strftime('%d/%m/%Y') }} - {{ nomina.periodo_fin.strftime('%d/%m/%Y') }}
6
+ </h3>
7
+ <div>
8
+ <a href="{{ url_for('planilla.listar_nominas', planilla_id=planilla.id) }}" class="btn btn-secondary">
9
+ <i class="fas fa-arrow-left"></i> Volver al Historial
10
+ </a>
11
+ </div>
12
+ </div>
13
+
14
+ {% if has_errors %}
15
+ <div class="alert alert-danger mb-4">
16
+ <h5><i class="fas fa-times-circle"></i> Esta Nómina Tiene Errores de Procesamiento</h5>
17
+ <p class="mb-2">No se puede aprobar ni aplicar esta nómina hasta que se corrijan los siguientes errores:</p>
18
+ <ul class="mb-2">
19
+ {% for error in error_messages %}
20
+ <li>{{ error }}</li>
21
+ {% endfor %}
22
+ </ul>
23
+ <p class="mb-0">
24
+ <strong>Acción requerida:</strong> Corrija los datos (tipo de cambio, configuración, etc.) y recalcule la nómina.
25
+ <a href="{{ url_for('planilla.ver_log_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}" class="alert-link">Ver log completo</a>
26
+ </p>
27
+ </div>
28
+ {% elif has_warnings %}
29
+ <div class="alert alert-warning mb-4">
30
+ <h5><i class="fas fa-exclamation-triangle"></i> Esta Nómina Tiene Advertencias</h5>
31
+ <p class="mb-2">La nómina se procesó pero hay situaciones que requieren su atención:</p>
32
+ <ul class="mb-2">
33
+ {% for warning in warning_messages %}
34
+ <li>{{ warning }}</li>
35
+ {% endfor %}
36
+ </ul>
37
+ <p class="mb-0">
38
+ <a href="{{ url_for('planilla.ver_log_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}" class="alert-link">Ver log completo</a>
39
+ </p>
40
+ </div>
41
+ {% endif %}
42
+
43
+ {% if nomina.estado == 'calculando' %}
44
+ <div class="card mb-4 border-primary">
45
+ <div class="card-header bg-primary text-white">
46
+ <h5 class="mb-0">
47
+ <i class="fas fa-spinner fa-spin"></i> Calculando Nómina en Segundo Plano
48
+ </h5>
49
+ </div>
50
+ <div class="card-body">
51
+ <p class="mb-3">
52
+ <strong>La nómina está siendo calculada en segundo plano.</strong>
53
+ El sistema no se ha colgado, por favor espere mientras se procesan todos los empleados.
54
+ </p>
55
+
56
+ <!-- Current employee being processed -->
57
+ <div class="alert alert-info mb-3" id="current-employee-alert">
58
+ <i class="fas fa-user-clock"></i>
59
+ <strong>Procesando:</strong>
60
+ <span id="empleado-actual">{{ nomina.empleado_actual or 'Iniciando...' }}</span>
61
+ </div>
62
+
63
+ <!-- Progress bar -->
64
+ <div class="progress mb-3" style="height: 30px;">
65
+ <div id="progress-bar" class="progress-bar progress-bar-striped progress-bar-animated bg-primary"
66
+ role="progressbar"
67
+ style="width: {% if nomina.total_empleados and nomina.total_empleados > 0 %}{{ (nomina.empleados_procesados / nomina.total_empleados * 100)|int }}%{% else %}0%{% endif %}"
68
+ aria-valuenow="{{ nomina.empleados_procesados or 0 }}"
69
+ aria-valuemin="0"
70
+ aria-valuemax="{{ nomina.total_empleados or 100 }}">
71
+ <span id="progress-text">
72
+ {% if nomina.total_empleados and nomina.total_empleados > 0 %}
73
+ {{ (nomina.empleados_procesados / nomina.total_empleados * 100)|int }}%
74
+ {% else %}
75
+ 0%
76
+ {% endif %}
77
+ ({{ nomina.empleados_procesados or 0 }}/{{ nomina.total_empleados or 0 }})
78
+ </span>
79
+ </div>
80
+ </div>
81
+
82
+ <!-- Statistics -->
83
+ <div class="row text-center mb-3">
84
+ <div class="col-md-4">
85
+ <h6 class="text-muted">Total Empleados</h6>
86
+ <h4 id="total-empleados">{{ nomina.total_empleados or 0 }}</h4>
87
+ </div>
88
+ <div class="col-md-4">
89
+ <h6 class="text-muted">Procesados</h6>
90
+ <h4 id="empleados-procesados" class="text-success">{{ nomina.empleados_procesados or 0 }}</h4>
91
+ </div>
92
+ <div class="col-md-4">
93
+ <h6 class="text-muted">Con Errores</h6>
94
+ <h4 id="empleados-con-error" class="text-danger">{{ nomina.empleados_con_error or 0 }}</h4>
95
+ </div>
96
+ </div>
97
+
98
+ <!-- Activity Log -->
99
+ <div class="card">
100
+ <div class="card-header bg-light">
101
+ <h6 class="mb-0">
102
+ <i class="fas fa-list"></i> Registro de Actividad
103
+ <button class="btn btn-sm btn-outline-secondary float-end" onclick="scrollLogToBottom()">
104
+ <i class="fas fa-arrow-down"></i> Ir al final
105
+ </button>
106
+ </h6>
107
+ </div>
108
+ <div class="card-body" style="max-height: 300px; overflow-y: auto; font-family: monospace; font-size: 0.9em; background-color: #f8f9fa;" id="activity-log">
109
+ {% if nomina.log_procesamiento %}
110
+ {% for entry in nomina.log_procesamiento %}
111
+ <div class="log-entry {% if entry.status == 'success' %}text-success{% elif entry.status == 'error' %}text-danger{% elif entry.status == 'warning' %}text-warning{% else %}text-muted{% endif %}">
112
+ {% if entry.status == 'error' %}<i class="fas fa-times-circle"></i> {% elif entry.status == 'warning' %}<i class="fas fa-exclamation-triangle"></i> {% endif %}{{ entry.message }}
113
+ </div>
114
+ {% endfor %}
115
+ {% else %}
116
+ <div class="text-muted">{{ _('No hay mensajes en el log de procesamiento para esta nómina.') }}</div>
117
+ {% endif %}
118
+ </div>
119
+ </div>
120
+
121
+ <div class="text-center mt-3">
122
+ <small class="text-muted">
123
+ <i class="fas fa-sync-alt fa-spin"></i>
124
+ Esta página se actualiza automáticamente cada 3 segundos
125
+ </small>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ {% elif nomina.estado == 'error' %}
130
+ <div class="alert alert-danger">
131
+ <h5><i class="fas fa-exclamation-triangle"></i> Error en el Cálculo de la Nómina</h5>
132
+ <p>Ocurrió un error durante el cálculo de la nómina. Detalles:</p>
133
+ {% if nomina.errores_calculo %}
134
+ <ul>
135
+ {% for key, error in nomina.errores_calculo.items() %}
136
+ <li>
137
+ <strong>{{ key }}:</strong>
138
+ {% if error is mapping %}
139
+ {{ error.critical_error or error.error or error }}
140
+ {% else %}
141
+ {{ error }}
142
+ {% endif %}
143
+ </li>
144
+ {% endfor %}
145
+ </ul>
146
+ {% endif %}
147
+ <p class="mb-3">
148
+ <strong>Empleados procesados:</strong> {{ nomina.empleados_procesados or 0 }} / {{ nomina.total_empleados or 0 }}<br>
149
+ <strong>Empleados con error:</strong> {{ nomina.empleados_con_error or 0 }}
150
+ </p>
151
+ <div>
152
+ <form action="{{ url_for('planilla.reintentar_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}"
153
+ method="post" style="display: inline;">
154
+ <button type="submit" class="btn btn-warning"
155
+ onclick="return confirm('¿Reintentar el procesamiento de esta nómina? Se limpiarán los datos parciales y se intentará procesar nuevamente.')">
156
+ <i class="fas fa-redo"></i> Reintentar Procesamiento
157
+ </button>
158
+ </form>
159
+ {% if nomina.errores_calculo and nomina.errores_calculo.get('is_recoverable') == False %}
160
+ <small class="text-muted d-block mt-2">
161
+ <i class="fas fa-info-circle"></i>
162
+ Este error no es recuperable automáticamente. Revise la configuración o los datos antes de reintentar.
163
+ </small>
164
+ {% endif %}
165
+ </div>
166
+ </div>
167
+ {% endif %}
168
+
169
+ {% if nomina.estado == 'generado' and nomina.empleados_con_error and nomina.empleados_con_error > 0 %}
170
+ <div class="alert alert-warning">
171
+ <h5><i class="fas fa-exclamation-circle"></i> Advertencia: Algunos Empleados No Se Procesaron Correctamente</h5>
172
+ <p>La nómina se generó exitosamente, pero {{ nomina.empleados_con_error }} empleado(s) tuvieron errores durante el cálculo.</p>
173
+ {% if nomina.errores_calculo %}
174
+ <details>
175
+ <summary><strong>Ver errores</strong></summary>
176
+ <ul class="mt-2">
177
+ {% for empleado_id, error in nomina.errores_calculo.items() %}
178
+ <li><strong>{{ empleado_id }}:</strong> {{ error }}</li>
179
+ {% endfor %}
180
+ </ul>
181
+ </details>
182
+ {% endif %}
183
+ </div>
184
+ {% endif %}
185
+
186
+ <div class="card mb-4">
187
+ <div class="card-header">
188
+ <div class="d-flex justify-content-between align-items-center">
189
+ <h5 class="mb-0">Resumen de la Nómina</h5>
190
+ <div>
191
+ {% if nomina.estado == 'generado' %}
192
+ <form action="{{ url_for('planilla.aprobar_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}"
193
+ method="post" style="display: inline;">
194
+ {% if has_errors %}
195
+ <button type="button" class="btn btn-success" disabled title="No se puede aprobar una nómina con errores de procesamiento">
196
+ <i class="fas fa-check"></i> Aprobar
197
+ </button>
198
+ {% else %}
199
+ <button type="submit" class="btn btn-success" onclick="return confirm('¿Aprobar esta nómina?')">
200
+ <i class="fas fa-check"></i> Aprobar
201
+ </button>
202
+ {% endif %}
203
+ </form>
204
+ {% elif nomina.estado == 'aprobado' %}
205
+ <form action="{{ url_for('planilla.aplicar_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}"
206
+ method="post" style="display: inline;">
207
+ <button type="submit" class="btn btn-primary" onclick="return confirm('¿Marcar como aplicada/pagada?')">
208
+ <i class="fas fa-money-bill"></i> Aplicar/Pagar
209
+ </button>
210
+ </form>
211
+ {% endif %}
212
+ {% if nomina.estado != 'aplicado' %}
213
+ <form action="{{ url_for('planilla.recalcular_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}"
214
+ method="post" style="display: inline;">
215
+ <button type="submit" class="btn btn-warning" onclick="return confirm('¿Recalcular esta nómina? Se eliminarán los datos actuales y se generarán nuevamente.')">
216
+ <i class="fas fa-sync-alt"></i> Recalcular
217
+ </button>
218
+ </form>
219
+ {% endif %}
220
+ <a href="{{ url_for('planilla.listar_novedades', planilla_id=planilla.id, nomina_id=nomina.id) }}" class="btn btn-info">
221
+ <i class="bi bi-list-check"></i> Novedades
222
+ </a>
223
+ <a href="{{ url_for('planilla.ver_log_nomina', planilla_id=planilla.id, nomina_id=nomina.id) }}" class="btn btn-outline-secondary">
224
+ <i class="fas fa-list-alt"></i> Log de Ejecución
225
+ </a>
226
+ {% if nomina.estado in ['generado', 'aprobado', 'aplicado'] %}
227
+ <div class="btn-group" role="group">
228
+ <button type="button" class="btn btn-secondary dropdown-toggle" data-bs-toggle="dropdown" aria-expanded="false">
229
+ <i class="fas fa-file-excel"></i> Exportar
230
+ </button>
231
+ <ul class="dropdown-menu">
232
+ <li>
233
+ <a class="dropdown-item" href="{{ url_for('planilla.exportar_nomina_excel', planilla_id=planilla.id, nomina_id=nomina.id) }}">
234
+ <i class="fas fa-file-excel"></i> Nómina (Excel)
235
+ </a>
236
+ </li>
237
+ <li>
238
+ <a class="dropdown-item" href="{{ url_for('planilla.exportar_prestaciones_excel', planilla_id=planilla.id, nomina_id=nomina.id) }}">
239
+ <i class="fas fa-file-excel"></i> Prestaciones (Excel)
240
+ </a>
241
+ </li>
242
+ <li>
243
+ <a class="dropdown-item" href="{{ url_for('planilla.exportar_comprobante_excel', planilla_id=planilla.id, nomina_id=nomina.id) }}">
244
+ <i class="fas fa-file-invoice"></i> Comprobante Contable (Excel)
245
+ </a>
246
+ </li>
247
+ <li>
248
+ <a class="dropdown-item" href="{{ url_for('planilla.exportar_comprobante_detallado_excel', planilla_id=planilla.id, nomina_id=nomina.id) }}">
249
+ <i class="fas fa-list-alt"></i> Comprobante Detallado por Empleado (Excel)
250
+ </a>
251
+ </li>
252
+ </ul>
253
+ </div>
254
+ {% endif %}
255
+ </div>
256
+ </div>
257
+ </div>
258
+ <div class="card-body">
259
+ <div class="row">
260
+ <div class="col-md-3">
261
+ <strong>Planilla:</strong><br>
262
+ {{ planilla.nombre }}
263
+ </div>
264
+ <div class="col-md-2">
265
+ <strong>Estado:</strong><br>
266
+ {% if nomina.estado == 'calculando' %}
267
+ <span class="badge bg-primary"><i class="fas fa-spinner fa-spin"></i> Calculando</span>
268
+ {% elif nomina.estado == 'generado' %}
269
+ <span class="badge bg-warning">Generado</span>
270
+ {% elif nomina.estado == 'aprobado' %}
271
+ <span class="badge bg-info">Aprobado</span>
272
+ {% elif nomina.estado == 'aplicado' %}
273
+ <span class="badge bg-success">Aplicado</span>
274
+ {% elif nomina.estado == 'error' %}
275
+ <span class="badge bg-danger">Error</span>
276
+ {% endif %}
277
+ </div>
278
+ <div class="col-md-2">
279
+ <strong>Moneda:</strong><br>
280
+ {{ planilla.moneda.codigo if planilla.moneda else 'N/A' }}
281
+ </div>
282
+ <div class="col-md-2">
283
+ <strong>Empleados:</strong><br>
284
+ {{ nomina_empleados|length }}
285
+ </div>
286
+ <div class="col-md-3">
287
+ <strong>Generado:</strong><br>
288
+ {{ nomina.fecha_generacion.strftime('%d/%m/%Y %H:%M') }}
289
+ {% if nomina.generado_por %}por {{ nomina.generado_por }}{% endif %}
290
+ </div>
291
+ </div>
292
+ <hr>
293
+ <div class="row">
294
+ <div class="col-md-4">
295
+ <div class="card bg-light">
296
+ <div class="card-body text-center">
297
+ <h6 class="text-muted mb-1">Total Bruto</h6>
298
+ <h4 class="text-primary mb-0">{{ planilla.moneda.simbolo if planilla.moneda else '' }} {{ "{:,.2f}".format(nomina.total_bruto|float) }}</h4>
299
+ </div>
300
+ </div>
301
+ </div>
302
+ <div class="col-md-4">
303
+ <div class="card bg-light">
304
+ <div class="card-body text-center">
305
+ <h6 class="text-muted mb-1">Total Deducciones</h6>
306
+ <h4 class="text-danger mb-0">{{ planilla.moneda.simbolo if planilla.moneda else '' }} {{ "{:,.2f}".format(nomina.total_deducciones|float) }}</h4>
307
+ </div>
308
+ </div>
309
+ </div>
310
+ <div class="col-md-4">
311
+ <div class="card bg-light">
312
+ <div class="card-body text-center">
313
+ <h6 class="text-muted mb-1">Total Neto</h6>
314
+ <h4 class="text-success mb-0">{{ planilla.moneda.simbolo if planilla.moneda else '' }} {{ "{:,.2f}".format(nomina.total_neto|float) }}</h4>
315
+ </div>
316
+ </div>
317
+ </div>
318
+ </div>
319
+ </div>
320
+ </div>
321
+
322
+ <div class="card">
323
+ <div class="card-header">
324
+ <h5 class="mb-0">Detalle por Empleado</h5>
325
+ </div>
326
+ <div class="card-body">
327
+ <table class="table table-striped table-hover">
328
+ <thead>
329
+ <tr>
330
+ <th>Empleado</th>
331
+ <th>Cargo</th>
332
+ <th class="text-right">Salario Base</th>
333
+ <th class="text-right">Ingresos</th>
334
+ <th class="text-right">Deducciones</th>
335
+ <th class="text-right">Neto</th>
336
+ <th>T/C</th>
337
+ <th>Acciones</th>
338
+ </tr>
339
+ </thead>
340
+ <tbody>
341
+ {% for ne in nomina_empleados %}
342
+ <tr>
343
+ <td>
344
+ <strong>{{ ne.empleado.primer_nombre }} {{ ne.empleado.primer_apellido }}</strong>
345
+ {% if ne.empleado.segundo_apellido %}{{ ne.empleado.segundo_apellido }}{% endif %}
346
+ </td>
347
+ <td>{{ ne.cargo_snapshot or ne.empleado.cargo or 'N/A' }}</td>
348
+ <td class="text-right">{{ "{:,.2f}".format(ne.sueldo_base_historico|float) }}</td>
349
+ <td class="text-right text-primary">+{{ "{:,.2f}".format(ne.total_ingresos|float) }}</td>
350
+ <td class="text-right text-danger">-{{ "{:,.2f}".format(ne.total_deducciones|float) }}</td>
351
+ <td class="text-right"><strong>{{ "{:,.2f}".format(ne.salario_neto|float) }}</strong></td>
352
+ <td>{{ "{:.4f}".format(ne.tipo_cambio_aplicado|float) if ne.tipo_cambio_aplicado else '1.0' }}</td>
353
+ <td>
354
+ <a href="{{ url_for('planilla.ver_nomina_empleado', planilla_id=planilla.id, nomina_id=nomina.id, nomina_empleado_id=ne.id) }}"
355
+ class="btn btn-sm btn-outline-primary" title="Ver Detalle">
356
+ <i class="bi bi-eye"></i>
357
+ </a>
358
+ </td>
359
+ </tr>
360
+ {% endfor %}
361
+ </tbody>
362
+ <tfoot>
363
+ <tr class="table-dark">
364
+ <th colspan="2">TOTALES</th>
365
+ <th class="text-right">-</th>
366
+ <th class="text-right">{{ "{:,.2f}".format(nomina.total_bruto|float) }}</th>
367
+ <th class="text-right">{{ "{:,.2f}".format(nomina.total_deducciones|float) }}</th>
368
+ <th class="text-right">{{ "{:,.2f}".format(nomina.total_neto|float) }}</th>
369
+ <th colspan="2"></th>
370
+ </tr>
371
+ </tfoot>
372
+ </table>
373
+ </div>
374
+ </div>
375
+
376
+ {% if nomina.estado == 'calculando' %}
377
+ <script>
378
+ // Auto-refresh progress for nominas in "calculando" state
379
+ (function() {
380
+ const progressBar = document.getElementById('progress-bar');
381
+ const progressText = document.getElementById('progress-text');
382
+ const totalEmpleados = document.getElementById('total-empleados');
383
+ const empleadosProcesados = document.getElementById('empleados-procesados');
384
+ const empleadosConError = document.getElementById('empleados-con-error');
385
+ const empleadoActual = document.getElementById('empleado-actual');
386
+ const activityLog = document.getElementById('activity-log');
387
+ let autoScroll = true;
388
+
389
+ // Function to scroll log to bottom
390
+ window.scrollLogToBottom = function() {
391
+ activityLog.scrollTop = activityLog.scrollHeight;
392
+ autoScroll = true;
393
+ };
394
+
395
+ // Detect manual scroll
396
+ activityLog.addEventListener('scroll', function() {
397
+ const isAtBottom = activityLog.scrollHeight - activityLog.scrollTop <= activityLog.clientHeight + 50;
398
+ autoScroll = isAtBottom;
399
+ });
400
+
401
+ function updateProgress() {
402
+ fetch('{{ url_for("planilla.progreso_nomina", planilla_id=planilla.id, nomina_id=nomina.id) }}')
403
+ .then(response => response.json())
404
+ .then(data => {
405
+ // Update progress bar
406
+ const progreso = data.progreso_porcentaje || 0;
407
+ progressBar.style.width = progreso + '%';
408
+ progressBar.setAttribute('aria-valuenow', progreso);
409
+ progressText.textContent = progreso + '% (' + data.empleados_procesados + '/' + data.total_empleados + ')';
410
+
411
+ // Update counters
412
+ totalEmpleados.textContent = data.total_empleados || 0;
413
+ empleadosProcesados.textContent = data.empleados_procesados || 0;
414
+ empleadosConError.textContent = data.empleados_con_error || 0;
415
+
416
+ // Update current employee
417
+ if (data.empleado_actual) {
418
+ empleadoActual.textContent = data.empleado_actual;
419
+ }
420
+
421
+ // Update activity log
422
+ if (data.log_procesamiento && data.log_procesamiento.length > 0) {
423
+ let logHtml = '';
424
+ data.log_procesamiento.forEach(entry => {
425
+ let statusClass = 'text-muted';
426
+ if (entry.status === 'success') {
427
+ statusClass = 'text-success';
428
+ } else if (entry.status === 'error') {
429
+ statusClass = 'text-danger';
430
+ }
431
+ logHtml += '<div class="log-entry ' + statusClass + '">' +
432
+ escapeHtml(entry.message) + '</div>';
433
+ });
434
+ activityLog.innerHTML = logHtml;
435
+
436
+ // Auto-scroll to bottom if user hasn't manually scrolled
437
+ if (autoScroll) {
438
+ activityLog.scrollTop = activityLog.scrollHeight;
439
+ }
440
+ }
441
+
442
+ // If calculation is complete, reload page to show results
443
+ if (data.estado !== 'calculando') {
444
+ setTimeout(() => {
445
+ window.location.reload();
446
+ }, 1000);
447
+ }
448
+ })
449
+ .catch(error => {
450
+ console.error('Error fetching progress:', error);
451
+ });
452
+ }
453
+
454
+ // Helper function to escape HTML
455
+ function escapeHtml(text) {
456
+ const div = document.createElement('div');
457
+ div.textContent = text;
458
+ return div.innerHTML;
459
+ }
460
+
461
+ // Update immediately
462
+ updateProgress();
463
+
464
+ // Poll every 3 seconds
465
+ const intervalId = setInterval(updateProgress, 3000);
466
+
467
+ // Clear interval when page is unloaded
468
+ window.addEventListener('beforeunload', () => {
469
+ clearInterval(intervalId);
470
+ });
471
+
472
+ // Initial scroll to bottom
473
+ scrollLogToBottom();
474
+ })();
475
+ </script>
476
+ {% endif %}
477
+ {% endblock %}