decidim-stratified_sortitions 0.0.1

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.
Files changed (124) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-AGPLv3.txt +661 -0
  3. data/README.md +91 -0
  4. data/Rakefile +42 -0
  5. data/app/cells/decidim/stratified_sortitions/stratified_sortition_cell.rb +21 -0
  6. data/app/cells/decidim/stratified_sortitions/stratified_sortition_l_cell.rb +22 -0
  7. data/app/cells/decidim/stratified_sortitions/stratified_sortition_metadata_cell.rb +63 -0
  8. data/app/commands/decidim/stratified_sortitions/admin/create_stratified_sortition.rb +84 -0
  9. data/app/commands/decidim/stratified_sortitions/admin/destroy_stratified_sortition.rb +49 -0
  10. data/app/commands/decidim/stratified_sortitions/admin/duplicate_stratified_sortition.rb +74 -0
  11. data/app/commands/decidim/stratified_sortitions/admin/import_sample.rb +40 -0
  12. data/app/commands/decidim/stratified_sortitions/admin/remove_uploaded_samples.rb +53 -0
  13. data/app/commands/decidim/stratified_sortitions/admin/update_stratified_sortition.rb +174 -0
  14. data/app/controllers/concerns/decidim/stratified_sortitions/orderable_stratified_sortitions.rb +37 -0
  15. data/app/controllers/concerns/decidim/stratified_sortitions/strata_charts_data.rb +91 -0
  16. data/app/controllers/decidim/stratified_sortitions/admin/application_controller.rb +19 -0
  17. data/app/controllers/decidim/stratified_sortitions/admin/samples_controller.rb +143 -0
  18. data/app/controllers/decidim/stratified_sortitions/admin/stratified_sortitions_controller.rb +235 -0
  19. data/app/controllers/decidim/stratified_sortitions/application_controller.rb +13 -0
  20. data/app/controllers/decidim/stratified_sortitions/charts_pdf_controller_helper.rb +17 -0
  21. data/app/controllers/decidim/stratified_sortitions/stratified_sortitions_controller.rb +61 -0
  22. data/app/forms/decidim/stratified_sortitions/admin/sample_upload_form.rb +15 -0
  23. data/app/forms/decidim/stratified_sortitions/admin/stratified_sortitions_form.rb +155 -0
  24. data/app/forms/decidim/stratified_sortitions/admin/stratum_form.rb +69 -0
  25. data/app/forms/decidim/stratified_sortitions/admin/substratum_form.rb +26 -0
  26. data/app/helpers/decidim/stratified_sortitions/admin/stratified_sortitions_helper.rb +18 -0
  27. data/app/helpers/decidim/stratified_sortitions/application_helper.rb +85 -0
  28. data/app/helpers/decidim/stratified_sortitions/charts_pdf_helper.rb +73 -0
  29. data/app/helpers/decidim/stratified_sortitions/stratified_sortition_cells_helper.rb +56 -0
  30. data/app/javascript/channels/sample_import_progress.js +12 -0
  31. data/app/jobs/decidim/stratified_sortitions/admin/execute_sortition_job.rb +21 -0
  32. data/app/jobs/decidim/stratified_sortitions/admin/import_sample_job.rb +170 -0
  33. data/app/jobs/decidim/stratified_sortitions/admin/remove_samples_job.rb +19 -0
  34. data/app/jobs/decidim/stratified_sortitions/admin/sortition_results_export_job.rb +28 -0
  35. data/app/mailers/decidim/stratified_sortitions/admin/import_mailer.rb +26 -0
  36. data/app/models/decidim/stratified_sortitions/application_record.rb +9 -0
  37. data/app/models/decidim/stratified_sortitions/panel_portfolio.rb +165 -0
  38. data/app/models/decidim/stratified_sortitions/sample_import.rb +14 -0
  39. data/app/models/decidim/stratified_sortitions/sample_participant.rb +12 -0
  40. data/app/models/decidim/stratified_sortitions/sample_participant_stratum.rb +11 -0
  41. data/app/models/decidim/stratified_sortitions/stratified_sortition.rb +74 -0
  42. data/app/models/decidim/stratified_sortitions/stratum.rb +22 -0
  43. data/app/models/decidim/stratified_sortitions/substratum.rb +19 -0
  44. data/app/packs/entrypoints/decidim_stratified_sortitions.js +10 -0
  45. data/app/packs/entrypoints/decidim_stratified_sortitions_admin.js +9 -0
  46. data/app/packs/entrypoints/decidim_stratified_sortitions_admin.scss +1 -0
  47. data/app/packs/images/decidim/stratified_sortitions/icon.svg +1 -0
  48. data/app/packs/src/decidim/stratified_sortitions/application.js +3 -0
  49. data/app/packs/src/decidim/stratified_sortitions/results_tabs.js +59 -0
  50. data/app/packs/src/decidim/stratified_sortitions/stratum_fields.js +302 -0
  51. data/app/packs/src/decidim/stratified_sortitions/substratum_fields.js +165 -0
  52. data/app/packs/src/decidim/stratified_sortitions/upload_sample.js +16 -0
  53. data/app/packs/stylesheets/decidim/stratified_sortitions/admin/stratified_sortitions.scss +210 -0
  54. data/app/packs/stylesheets/decidim/stratified_sortitions/charts_pdf.scss +148 -0
  55. data/app/packs/stylesheets/stratified_sortitions.scss +56 -0
  56. data/app/permissions/decidim/stratified_sortitions/admin/permissions.rb +74 -0
  57. data/app/permissions/decidim/stratified_sortitions/permissions.rb +23 -0
  58. data/app/presenters/decidim/stratified_sortitions/admin_log/stratified_sortition_presenter.rb +35 -0
  59. data/app/queries/decidim/stratified_sortitions/filtered_stratified_sortitions.rb +39 -0
  60. data/app/serializers/decidim/stratified_sortitions/sortition_result_serializer.rb +116 -0
  61. data/app/services/decidim/stratified_sortitions/charts_pdf_generator.rb +96 -0
  62. data/app/services/decidim/stratified_sortitions/fair_sortition_service.rb +247 -0
  63. data/app/services/decidim/stratified_sortitions/leximin/constraint_builder.rb +180 -0
  64. data/app/services/decidim/stratified_sortitions/leximin/distribution_solver.rb +217 -0
  65. data/app/services/decidim/stratified_sortitions/leximin/feasibility_checker.rb +222 -0
  66. data/app/services/decidim/stratified_sortitions/leximin/panel_generator.rb +149 -0
  67. data/app/services/decidim/stratified_sortitions/leximin/panel_sampler.rb +125 -0
  68. data/app/services/decidim/stratified_sortitions/leximin_selector.rb +136 -0
  69. data/app/services/decidim/stratified_sortitions/sortition_results_exporter.rb +161 -0
  70. data/app/views/decidim/admin/components/index.html.erb +54 -0
  71. data/app/views/decidim/stratified_sortitions/admin/import_mailer/import.html.erb +22 -0
  72. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_execute_modal.html.erb +19 -0
  73. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_confirm_participants_modal.html.erb +20 -0
  74. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_form.html.erb +55 -0
  75. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_navigation_menu.html.erb +10 -0
  76. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_strata.html.erb +22 -0
  77. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_stratum.html.erb +75 -0
  78. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substrata.html.erb +34 -0
  79. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/_substratum.html.erb +54 -0
  80. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/edit.html.erb +21 -0
  81. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/execute.html.erb +155 -0
  82. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/export_charts_pdf.html.erb +38 -0
  83. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/index.html.erb +75 -0
  84. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/new.html.erb +21 -0
  85. data/app/views/decidim/stratified_sortitions/admin/stratified_sortitions/upload_sample.html.erb +117 -0
  86. data/app/views/decidim/stratified_sortitions/shared/_strata_charts.html.erb +21 -0
  87. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_results_count.html.erb +15 -0
  88. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortition.html.erb +1 -0
  89. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_stratified_sortitions.html.erb +13 -0
  90. data/app/views/decidim/stratified_sortitions/stratified_sortitions/_tags.html.erb +9 -0
  91. data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.html.erb +22 -0
  92. data/app/views/decidim/stratified_sortitions/stratified_sortitions/index.js.erb +5 -0
  93. data/app/views/decidim/stratified_sortitions/stratified_sortitions/show.html.erb +142 -0
  94. data/app/views/layouts/decidim/admin/_sidebar_menu.html.erb +45 -0
  95. data/app/views/layouts/decidim/stratified_sortitions/charts_pdf.html.erb +14 -0
  96. data/config/assets.rb +24 -0
  97. data/config/locales/ca.yml +361 -0
  98. data/config/locales/en.yml +360 -0
  99. data/config/locales/es.yml +360 -0
  100. data/config/locales/oc.yml +2 -0
  101. data/config/routes.rb +1 -0
  102. data/db/migrate/20251023103900_create_decidim_stratified_sortitions_stratified_sortitions.rb +18 -0
  103. data/db/migrate/20251104120000_create_stratified_sortitions_strata_and_substrata.decidim_stratified_sortitions.rb +27 -0
  104. data/db/migrate/20251219120000_create_sample_imports.rb +17 -0
  105. data/db/migrate/20251219120100_create_sample_participants.rb +19 -0
  106. data/db/migrate/20251219120200_create_sample_participant_strata.rb +16 -0
  107. data/db/migrate/20251230130000_change_value_to_text_in_substrata.rb +11 -0
  108. data/db/migrate/20260102114744_add_status_to_stratified_sortitions.rb +11 -0
  109. data/db/migrate/20260109105643_allow_null_value_in_substrata.rb +10 -0
  110. data/db/migrate/20260113121726_add_position_to_strata_and_substrata.rb +8 -0
  111. data/db/migrate/20260127100000_rename_weighing_to_max_quota_percentage.rb +12 -0
  112. data/db/migrate/20260127120000_create_panel_portfolios.rb +46 -0
  113. data/db/migrate/20260518000000_add_foreign_key_to_stratified_sortitions_component.rb +10 -0
  114. data/db/migrate/20260521000000_add_cascade_to_stratified_sortitions_foreign_keys.rb +64 -0
  115. data/db/migrate/20260616000000_add_execution_error_to_stratified_sortitions.rb +11 -0
  116. data/lib/decidim/stratified_sortitions/admin.rb +10 -0
  117. data/lib/decidim/stratified_sortitions/admin_engine.rb +49 -0
  118. data/lib/decidim/stratified_sortitions/component.rb +41 -0
  119. data/lib/decidim/stratified_sortitions/engine.rb +34 -0
  120. data/lib/decidim/stratified_sortitions/seeds.rb +24 -0
  121. data/lib/decidim/stratified_sortitions/test/factories.rb +80 -0
  122. data/lib/decidim/stratified_sortitions/version.rb +13 -0
  123. data/lib/decidim/stratified_sortitions.rb +29 -0
  124. metadata +222 -0
@@ -0,0 +1,360 @@
1
+ ---
2
+ es:
3
+ activemodel:
4
+ attributes:
5
+ stratified_sortition:
6
+ created_at: Fecha de creación
7
+ title: Título
8
+ description: Descripción
9
+ num_candidates: Número de personas candidatas a seleccionar
10
+ selection_criteria: Criterios de elegibilidad
11
+ selected_profiles_description: Descripción de los perfiles a seleccionar
12
+ errors:
13
+ messages:
14
+ cannot_add_strata_with_sample_participants: "No se pueden añadir estratos cuando ya hay participantes importados"
15
+ cannot_delete_strata_with_sample_participants: "No se pueden eliminar estratos cuando ya hay participantes importados"
16
+ cannot_change_stratum_name_with_sample_participants: "No se puede cambiar el nombre del estrato cuando ya hay participantes importados"
17
+ cannot_change_stratum_kind_with_sample_participants: "No se puede cambiar el tipo del estrato cuando ya hay participantes importados"
18
+ cannot_add_substrata_with_sample_participants: "No se pueden añadir subestratos cuando ya hay participantes importados"
19
+ cannot_delete_substrata_with_sample_participants: "No se pueden eliminar subestratos cuando ya hay participantes importados"
20
+ cannot_change_substratum_name_with_sample_participants: "No se puede cambiar el nombre del subestrato cuando ya hay participantes importados"
21
+ cannot_change_substratum_value_with_sample_participants: "No se puede cambiar el valor del subestrato cuando ya hay participantes importados"
22
+ cannot_change_substratum_range_with_sample_participants: "No se puede cambiar el rango del subestrato cuando ya hay participantes importados"
23
+ cannot_change_num_candidates_when_executed: "No se puede cambiar el número de candidatos después de que el sorteo haya sido ejecutado"
24
+ decidim:
25
+ admin:
26
+ components:
27
+ index:
28
+ headers:
29
+ scope: Ámbito
30
+ components:
31
+ stratified_sortitions:
32
+ name: Sorteos estratificados
33
+ settings:
34
+ global:
35
+ announcement: Aviso
36
+ publish_sortitions: Publicar gráficos comparativos de los sorteos
37
+ step:
38
+ announcement: Aviso
39
+ stratified_sortitions:
40
+ admin:
41
+ actions:
42
+ configure: Configurar
43
+ confirm_destroy: ¿Estás seguro de que quieres eliminar este %{name}?
44
+ destroy: Eliminar
45
+ duplicate: Duplicar
46
+ execute: Ejecutar sorteo
47
+ new_stratified_sortition: "Nuevo sorteo estratificado"
48
+ title: "Acciones"
49
+ census_management: Gestionar muestra
50
+ models:
51
+ stratified_sortition:
52
+ fields:
53
+ created_at: Fecha de creación
54
+ title: Título
55
+ description: Descripción
56
+ num_candidates: Número de candidatas a seleccionar
57
+ selection_criteria: Criterios de elegibilidad
58
+ selected_profiles_description: Descripción de los perfiles a seleccionar
59
+ status: Estado
60
+ stratified_sortitions:
61
+ create:
62
+ error: Ha habido un problema al crear este sorteo estratificado
63
+ success: Sorteo estratificado creado con éxito
64
+ destroy:
65
+ error: Ha habido un problema al eliminar este sorteo estratificado
66
+ has_problems: Este sorteo estratificado no se pudo eliminar debido a problemas relacionados
67
+ success: Sorteo estratificado eliminado con éxito
68
+ execute:
69
+ candidates: Candidatos
70
+ chart_results: Gráficos de distribución
71
+ tab_charts: Gráficos comparativos
72
+ tab_participants: Participantes seleccionados
73
+ empty_sample_participants: No se puede ejecutar el sorteo sin un censo cargado
74
+ execute_stratified_sortition: Ejecutar sorteo estratificado
75
+ confirm_execute_title: Confirmación
76
+ confirm_execute_body: Al ejecutar el sorteo este quedará registrado a la plataforma y la acción será irreversible. No se podrán editar los datos, la configuración de los estratos ni la muestra.
77
+ confirm_execute_ok: Ejecutar sorteo
78
+ confirm_execute_cancel: Cancelar
79
+ export: Exportar
80
+ export_results: Exportar resultados
81
+ export_csv: Resultados del sorteo estratificado como CSV
82
+ export_excel: Resultados del sorteo estratificado como Excel
83
+ export_json: Resultados del sorteo estratificado como JSON
84
+ export_charts_pdf: Exportar gráficos PDF
85
+ pdf_title: "Resultados del sorteo %{name}"
86
+ pdf_subtitle: Gráficos comparativos de distribución de la muestra
87
+ pdf_belongs_to: "Pertenece a: %{space_name}"
88
+ pdf_algorithm: "Algoritmo usado: %{algorithm}"
89
+ pdf_executed_at: "Sorteo realizado el: %{date}"
90
+ pdf_no_data: Sin datos
91
+ algorithm: Algoritmo
92
+ generated_at: Generado el
93
+ generation_time: Tiempo de generación
94
+ total_participants: Total de participantes
95
+ num_panels: Número de paneles
96
+ selected_at: Seleccionado el
97
+ verification_seed: Seed de verificación
98
+ random_value_used: Valor aleatorio utilizado
99
+ selected_panel_probability: Probabilidad del panel seleccionado
100
+ selected_participants_title: Participantes seleccionados
101
+ selected_participants:
102
+ one: "1 participante seleccionado"
103
+ other: "%{count} participantes seleccionados"
104
+ participant_id: ID
105
+ personal_data_1: Dato personal 1
106
+ personal_data_2: Dato personal 2
107
+ personal_data_3: Dato personal 3
108
+ personal_data_4: Dato personal 4
109
+ no_results_yet: El sorteo aún no se ha ejecutado.
110
+ confirm_participants_title: Muestra el listado de las participantes seleccionadas
111
+ confirm_participants_body: Estás accediendo a información y datos personales. Esta acción quedará registrada.
112
+ confirm_participants_ok: Muestra
113
+ confirm_participants_cancel: Cancelar
114
+ results: Resultados
115
+ executing_notice: El sorteo se está procesando en segundo plano. Esta página se actualizará cuando haya finalizado.
116
+ execution_failed: "La ejecución del sorteo ha fallado:"
117
+ success: Sorteo estratificado ejecutado con éxito
118
+ executing: El sorteo ha sido encolado y se está procesando en segundo plano.
119
+ target: Objetivo
120
+ title: Ejecutar sorteo estratificado
121
+ export_results:
122
+ no_results: No hay resultados para exportar. Ejecute el sorteo primero.
123
+ duplicate:
124
+ error: Ha habido un problema al duplicar este sorteo estratificado
125
+ success: Sorteo estratificado duplicado con éxito
126
+ index:
127
+ selected: Sorteos estratificados seleccionados
128
+ title: Sorteos estratificados
129
+ new:
130
+ create: Crear
131
+ title: Nuevo sorteo estratificado
132
+ edit:
133
+ title: Editar sorteo estratificado
134
+ update: Actualizar
135
+ form:
136
+ data: Información general
137
+ description: Descripción
138
+ name: Nombre
139
+ published: Publicado
140
+ selected_profiles_description_help: Puedes indicar, si corresponde, los criterios de estratificación utilizados en el sorteo, y la razón.
141
+ selection_criteria_help: Puedes indicar, si corresponde, los criterios de elegibilidad de las personas que han sido seleccionadas para participar en el sorteo.
142
+ strata:
143
+ title: Estratos
144
+ add_stratum: Añadir estrato
145
+ stratum:
146
+ up: Subir
147
+ down: Bajar
148
+ remove: Eliminar
149
+ name: Nombre
150
+ kind:
151
+ numeric_range: Rango numérico
152
+ title: Tipo
153
+ value: Valor
154
+ substrata_title: Subestratos
155
+ title: Estrato
156
+ substrata:
157
+ add_substratum: Añadir subestrato
158
+ substratum:
159
+ up: Subir
160
+ down: Bajar
161
+ remove: Eliminar
162
+ name: Nombre
163
+ value: Valor
164
+ value_placeholder: Valor
165
+ range: Rango
166
+ range_placeholder: Rango (p. ej. 0-18)
167
+ title: Subestrato
168
+ max_quota_percentage: Objetivo %
169
+ max_quota_percentage_placeholder: Objetivo %
170
+ update:
171
+ error: Ha habido un problema al actualizar este sorteo estratificado
172
+ success: Sorteo estratificado actualizado con éxito
173
+ upload_sample:
174
+ age: Grupo de edad
175
+ candidates: Candidatos
176
+ census_file: Elegir un archivo
177
+ census_file_label: Archivo excel.csv con los datos del censo
178
+ configure_strata_first: Primero debes configurar los estratos y subestratos
179
+ count_uploaded_files: "<b>%{count}</b> registros cargados."
180
+ description: El archivo de la muestra tiene que ser un CSV y tiene que tener un formatsimilar al del ejemplo siguiente:<br>Puedes descargar y utilizar la plantilla.
181
+ download_template: "Descargar plantilla"
182
+ download_template_csv: "Descargar plantilla como CSV"
183
+ download_template_xlsx: "Descargar plantilla como XLSX"
184
+ example: "Dato candidato 1,Dato candidato 2,Dato candidato 3,Dato candidato 4,Estrato 1,Estrato 2,Estrato 3,Estrato N<br>example@example.com,D,Superiores,34<br>example@example.com,E,Superiores,24<br><br>. . ."
185
+ example_headers:
186
+ personal_data_1: "Dato personal 1 (identificador único)"
187
+ personal_data_2: "Dato personal 2"
188
+ personal_data_3: "Dato personal 3"
189
+ personal_data_4: "Dato personal 4"
190
+ gender: "Género"
191
+ age: "Edad"
192
+ example_rows:
193
+ - - "11111111X"
194
+ - "Nombre ejemplo 1"
195
+ - "Teléfono Ejemplo 1"
196
+ - "correo1@ejemplo.com"
197
+ - "Hombre"
198
+ - "45"
199
+ - - "22222222Y"
200
+ - "Nombre ejemplo 2"
201
+ - "Teléfono Ejemplo 2"
202
+ - "correo2@ejemplo.com"
203
+ - "Mujer"
204
+ - "42"
205
+ execute_stratified_sortition: Ejecutar sorteo
206
+ execute_stratified_sortition_help: El sorteo no se puede ejecutar hasta que no se carguen registros.
207
+ filename_data: "%{filename}, el día %{date}, número de registros %{count}"
208
+ gender: Género
209
+ last_upload_date: La última carga fue el día %{date}
210
+ help: Las cuatro primeras columnas son datos del candidato, por ejemplo correo electrónico, DNI, teléfono o dirección y las siguientes columnas son los estratos definidos en el sorteo estratificado. Cada fila representa un candidato.
211
+ no_data: No hay datos
212
+ place: Lugar de nacimiento
213
+ remove_uploaded_samples: Eliminar todos los registros
214
+ confirm_remove_uploaded_samples: ¿Estás seguro de que quieres eliminar todas las muestras cargadas?
215
+ strata_not_configured: Primero debes configurar los estratos y subestratos
216
+ target: Objetivo
217
+ title: Gestionar muestra
218
+ upload_census: Subir una nueva muestra
219
+ upload_file: Subir nueva muestra
220
+ uploaded_file: Datos de la muestra cargada
221
+ uploaded_files: "Se han agregado los siguientes archivos:"
222
+ no_uploaded_files: "No hay archivos cargados."
223
+ help_csv: "<b>El archivo a cargar tiene que seguir las siguientes instrucciones:</b>
224
+ <ul><li>El formato del fichero tiene que ser .csv. Puedes descargar una plantilla.</li>
225
+ <li>La primera columna es obligatoria. Es el identificador único de cada participante y puede ser el DNI, un código generado aleatoriamente o cualquier otra forma de identificación que se quiera usar.</li>
226
+ <li>Las columnas 2,3 y 4 (Dato personal 2, 3, 4) son opcionales y no se utilizan para hacer el sorteo. Sirven para añadir los datos de contacto de cada participante.</li>
227
+ <li>Las columnas de los estratos son obligatorias y tienen que tener el mismo orden y nombre que el que hayamos configurado en el apartado anterior.</li>
228
+ <li>La información a incluir en las columnas tiene que coincidir con alguno de los Valores que hayamos indicado para cada estrato.</li>
229
+ <li>Si una participante no tiene datos en alguno de los estratos o no coincide con alguno de los valores configurados, será excluida del sorteo.</li></ul>"
230
+ help_submit: 'Al "Guardar" el sistema realizará la carga en segundo plano, puede tardar varios minutos dependiendo del volumen de participantes, por lo que al finalizar la carga será notificado vía email con el resultado.'
231
+ import_mailer:
232
+ subject: "Resultado de la importación de la muestra"
233
+ import:
234
+ ready: "La importación de la muestra ha finalizado correctamente."
235
+ total_rows: "Total de filas"
236
+ imported_rows: "Importadas correctamente"
237
+ failed_rows: "Filas con errores"
238
+ errors_summary: "Total de filas: %{total}. Importadas correctamente: %{imported}. Filas con errores: %{failed}."
239
+ see_details: "Los identificadores de los participantes los cuales han tenido error son:"
240
+ participant_id: "Identificador del participante:"
241
+ samples:
242
+ template:
243
+ personal_data_1: "Dato personal 1 (identificador único)"
244
+ personal_data_2: "Dato personal 2"
245
+ personal_data_3: "Dato personal 3"
246
+ personal_data_4: "Dato personal 4"
247
+ import_success: "La muestra se ha importado correctamente."
248
+ import_failed: "Ha ocurrido un error al importar la muestra: %{error}"
249
+ errors:
250
+ no_file: "No se ha seleccionado ningún archivo."
251
+ no_strata: "Debes configurar los estratos y subestratos antes de subir una muestra."
252
+ pending: "Solo puedes subir muestras cuando el sorteo estratificado está en estado pendiente."
253
+ sample_imports:
254
+ create:
255
+ success: "La muestra se está cargando en segundo plano. Cuando termine recibirás un correo con el resumen de la importación."
256
+ remove_uploaded_samples:
257
+ enqueued: "La eliminación de la muestra ha sido encolada y se procesará en segundo plano."
258
+ success: "Las muestras cargadas se han eliminado correctamente."
259
+ error: "Ha ocurrido un error al eliminar las muestras cargadas."
260
+ stratified_sortitions:
261
+ count:
262
+ candidates_count:
263
+ one: 1 candidato
264
+ other: "%{count} candidatos"
265
+ filters:
266
+ all: Todos
267
+ pending: Pendiente
268
+ executing: Ejecutando
269
+ executed: Ejecutado
270
+ failed: Fallido
271
+ state: Estado
272
+ index:
273
+ empty: Aún no hay sorteos estratificados.
274
+ orders:
275
+ label: 'Ordenar sorteos estratificados por:'
276
+ random: Aleatorio
277
+ recent: Reciente
278
+ results_count:
279
+ count:
280
+ one: Candidato a seleccionar
281
+ other: Candidatos a seleccionar
282
+ count_selected:
283
+ one: Candidato seleccionado
284
+ other: Candidatos seleccionados
285
+ show:
286
+ introduction: 'Esta página contiene los detalles del sorteo estratificado %{reference}. Mediante este sorteo, se han seleccionado %{num_candidates} candidatos utilizando un algoritmo de selección aleatoria estratificada que garantiza la representación justa en todos los estratos definidos.'
287
+ description: Descripción
288
+ selection_criteria: Criterios de elegibilidad
289
+ selected_profiles_description: Descripción de los perfiles seleccionados
290
+ to_be_selected_profiles_description: Descripción de los perfiles a seleccionar
291
+ strata: Estratos
292
+ max_quota: Cuota máxima
293
+ selected_participants: Participantes seleccionados
294
+ selected_participants_count:
295
+ one: Se ha seleccionado 1 participante
296
+ other: "Se han seleccionado %{count} participantes"
297
+ sortition_details: Detalles del sorteo
298
+ num_candidates: Número de candidatas a seleccionar
299
+ status: Estado
300
+ strata_count: Número de estratos
301
+ algorithm: Algoritmo
302
+ algorithm_name: LEXIMIN
303
+ algorithm_description: Algoritmo de selección aleatoria estratificada que garantiza la representación justa en todos los estratos definidos
304
+ reproducibility_details: Reproducibilidad del sorteo
305
+ verification_seed: Semilla de verificación
306
+ random_value_used: Valor aleatorio utilizado
307
+ selected_panel_index: Índice del panel seleccionado
308
+ selected_at: Seleccionado el
309
+ strata_charts: Distribución de los perfiles
310
+ target: Objetivo a conseguir
311
+ candidates: Candidaturas recibidas
312
+ no_data: Sin datos
313
+ results: Resultados del sorteo
314
+ statuses:
315
+ pending: Pendiente
316
+ executing: Ejecutando
317
+ executed: Ejecutado
318
+ failed: Fallido
319
+ stratified_sortition:
320
+ num_candidates: candidatos a seleccionar
321
+ strata_count: estratos
322
+ stratified_sortitions_count:
323
+ count:
324
+ one: 1 sorteo estratificado
325
+ other: "%{count} sorteos estratificados"
326
+ admin_log:
327
+ stratified_sortition:
328
+ create: "%{user_name} ha creado el sorteo estratificado %{resource_name} en %{space_name}"
329
+ update: "%{user_name} ha actualizado el sorteo estratificado %{resource_name} en %{space_name}"
330
+ delete: "%{user_name} ha eliminado el sorteo estratificado %{resource_name} en %{space_name}"
331
+ duplicate: "%{user_name} ha duplicado el sorteo estratificado %{resource_name}"
332
+ import_sample: "%{user_name} ha importado una muestra en el sorteo estratificado %{resource_name} en %{space_name}"
333
+ remove_samples: "%{user_name} ha eliminado los registros importados del sorteo estratificado %{resource_name} en %{space_name}"
334
+ execute: "%{user_name} ha ejecutado el sorteo estratificado %{resource_name} en %{space_name}"
335
+ export_results: "%{user_name} ha exportado los resultados del sorteo estratificado %{resource_name} en %{space_name}"
336
+ view_participants: "%{user_name} ha consultado el listado de participantes seleccionadas del sorteo estratificado %{resource_name} en %{space_name}"
337
+ errors:
338
+ panel_portfolio:
339
+ already_sampled: "La cartera ya ha sido muestreada"
340
+ probabilities_size_mismatch: "deben tener el mismo tamaño que los paneles (%{panels_count} paneles, %{probabilities_count} probabilidades)"
341
+ probabilities_sum_invalid: "deben sumar 1.0 (actualmente %{total})"
342
+ feasibility:
343
+ panel_size_positive: "El tamaño del panel (num_candidates) debe ser un número positivo"
344
+ no_categories: "No hay categorías (subestratos) definidas. Debe configurar los estratos y subestratos antes de ejecutar el algoritmo"
345
+ no_volunteers: "No hay voluntarios en el pool. Debe importar participantes antes de ejecutar el sorteo"
346
+ pool_too_small: "El pool de voluntarios (%{volunteers_count}) es más pequeño que el tamaño del panel (%{panel_size}). Agregue más participantes o reduzca el número de candidatos a seleccionar"
347
+ stratum_insufficient_quotas: "El estrato '%{stratum_name}' tiene cuotas máximas insuficientes. La suma de cuotas máximas (%{total_max}) es menor que el tamaño del panel (%{panel_size})"
348
+ stratum_percentages_exceed: "El estrato '%{stratum_name}' tiene porcentajes que suman más del 100%% (%{total_percentage}%%)"
349
+ substratum_insufficient_volunteers: "El subestrato '%{substratum_name}' del estrato '%{stratum_name}' requiere un mínimo de %{min_quota} voluntarios pero solo tiene %{volunteers_count}"
350
+ substratum_quota_exceeds_volunteers: "El subestrato '%{substratum_name}' del estrato '%{stratum_name}' tiene una cuota máxima de %{max_quota} (%{percentage}%%) pero solo %{volunteers_count} voluntario(s) pertenecen a esta categoría. Reduzca el porcentaje de cuota máxima o añada más participantes en esta categoría"
351
+ volunteers_missing_strata: "Hay %{count} voluntario(s) que no tienen asignado un subestrato para cada estrato. Esto puede causar problemas en el algoritmo de selección"
352
+ no_name: "Sin nombre"
353
+ fair_sortition:
354
+ already_performed: "El sorteo ya se ha realizado para este proceso"
355
+ portfolio_generation_failed: "Error generando la cartera de paneles: %{error}"
356
+ no_portfolio: "No hay cartera de paneles. Ejecute `generate_portfolio` primero"
357
+ sampling_failed: "Error en el muestreo: %{error}"
358
+ leximin:
359
+ no_feasible_panel: "No se pudo encontrar un panel inicial válido. La combinación de restricciones de cuota (porcentajes máximos) y la muestra disponible hacen imposible formar un panel. Posibles causas: (1) Los porcentajes de cuota máxima de algunos subestratos son demasiado restrictivos para el tamaño de la muestra disponible. (2) No hay suficientes participantes en ciertas combinaciones de subestratos. (3) El número de candidatos a seleccionar es demasiado grande en relación a la muestra. Intente ajustar los porcentajes de cuota máxima o cargue una muestra más grande."
360
+ internal_error: "Error interno en el algoritmo de selección: %{error}. Por favor, verifique que la configuración de los estratos y los porcentajes de cuota máxima son compatibles con la muestra cargada."
@@ -0,0 +1,2 @@
1
+ ---
2
+ oc:
data/config/routes.rb ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDecidimStratifiedSortitionsStratifiedSortitions < ActiveRecord::Migration[5.2]
4
+ def change
5
+ create_table :decidim_stratified_sortitions_stratified_sortitions do |t|
6
+ t.jsonb :title
7
+ t.integer :num_candidates, null: false
8
+ t.jsonb :description
9
+ t.jsonb :selection_criteria
10
+ t.jsonb :selected_profiles_description
11
+ t.references :decidim_component, index: false, null: false
12
+
13
+ t.timestamps
14
+ end
15
+
16
+ add_index :decidim_stratified_sortitions_stratified_sortitions, :decidim_component_id, name: "index_decidim_stratified_sortitions_component_id"
17
+ end
18
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateDecidimStratifiedSortitionsStrataAndSubstrata < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :decidim_stratified_sortitions_strata do |t|
6
+ t.jsonb :name, null: false, default: "{}"
7
+ t.string :kind, null: false
8
+ t.integer :decidim_stratified_sortition_id, null: false
9
+
10
+ t.timestamps
11
+ end
12
+
13
+ add_index :decidim_stratified_sortitions_strata, :decidim_stratified_sortition_id, name: "index_strata_on_sortition_id"
14
+
15
+ create_table :decidim_stratified_sortitions_substrata do |t|
16
+ t.jsonb :name, null: false, default: "{}"
17
+ t.jsonb :value, null: false, default: "{}"
18
+ t.string :range
19
+ t.string :weighing
20
+ t.integer :decidim_stratified_sortitions_stratum_id, null: false
21
+
22
+ t.timestamps
23
+ end
24
+
25
+ add_index :decidim_stratified_sortitions_substrata, :decidim_stratified_sortitions_stratum_id, name: "index_substrata_on_stratum_id"
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSampleImports < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :decidim_stratified_sortitions_sample_imports do |t|
6
+ t.references :stratified_sortition, null: false, foreign_key: { to_table: :decidim_stratified_sortitions_stratified_sortitions },
7
+ index: { name: "idx_sample_imports_on_sortition_id" }
8
+ t.string :filename
9
+ t.integer :total_rows, default: 0
10
+ t.integer :imported_rows, default: 0
11
+ t.integer :failed_rows, default: 0
12
+ t.jsonb :import_errors, default: {}
13
+ t.string :status, default: "pending"
14
+ t.timestamps
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSampleParticipants < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :decidim_stratified_sortitions_sample_participants do |t|
6
+ t.references :decidim_stratified_sortition, null: false, foreign_key: { to_table: :decidim_stratified_sortitions_stratified_sortitions },
7
+ index: { name: "idx_sample_part_on_sortition_id" }
8
+ t.references :decidim_stratified_sortitions_sample_import, foreign_key: { to_table: :decidim_stratified_sortitions_sample_imports },
9
+ index: { name: "idx_sample_part_on_sample_import_id" }
10
+ t.string :personal_data_1, null: false
11
+ t.string :personal_data_2
12
+ t.string :personal_data_3
13
+ t.string :personal_data_4
14
+ t.jsonb :column_values, default: []
15
+ t.string :reference_code
16
+ t.timestamps
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ class CreateSampleParticipantStrata < ActiveRecord::Migration[7.0]
4
+ def change
5
+ create_table :decidim_stratified_sortitions_sample_participant_strata do |t|
6
+ t.references :decidim_stratified_sortitions_sample_participant, null: false, foreign_key: true, index: { name: "idx_sample_part_strata_on_participants" }
7
+ t.references :decidim_stratified_sortitions_stratum, null: false, foreign_key: true, index: { name: "idx_sample_part_strata_on_strata" }
8
+ t.references :decidim_stratified_sortitions_substratum,
9
+ null: true,
10
+ foreign_key: { to_table: :decidim_stratified_sortitions_substrata },
11
+ index: { name: "idx_sample_part_strata_on_substrata" }
12
+ t.string :raw_value
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ChangeValueToTextInSubstrata < ActiveRecord::Migration[7.0]
4
+ def up
5
+ change_column :decidim_stratified_sortitions_substrata, :value, :text
6
+ end
7
+
8
+ def down
9
+ change_column :decidim_stratified_sortitions_substrata, :value, :jsonb
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddStatusToStratifiedSortitions < ActiveRecord::Migration[7.0]
4
+ def up
5
+ add_column :decidim_stratified_sortitions_stratified_sortitions, :status, :string, default: "pending"
6
+ end
7
+
8
+ def down
9
+ remove_column :decidim_stratified_sortitions_stratified_sortitions, :status
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AllowNullValueInSubstrata < ActiveRecord::Migration[7.0]
4
+ def change
5
+ change_table :decidim_stratified_sortitions_substrata, bulk: true do |t|
6
+ t.change_null :value, true
7
+ t.change_default :value, nil
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddPositionToStrataAndSubstrata < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_column :decidim_stratified_sortitions_substrata, :position, :integer
6
+ add_column :decidim_stratified_sortitions_strata, :position, :integer
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This migration renames the 'weighing' column to 'max_quota_percentage' for clarity.
4
+ # The column stores the target percentage for each substratum category.
5
+ # For the LEXIMIN algorithm:
6
+ # - min_quota is implicitly 0 (no minimum required for any category)
7
+ # - max_quota is calculated as: (max_quota_percentage / 100.0) * panel_size
8
+ class RenameWeighingToMaxQuotaPercentage < ActiveRecord::Migration[7.0]
9
+ def change
10
+ rename_column :decidim_stratified_sortitions_substrata, :weighing, :max_quota_percentage
11
+ end
12
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Migration to create the panel_portfolios table for storing
4
+ # LEXIMIN-generated panels before sampling.
5
+ #
6
+ # This enables a two-phase sortition process:
7
+ # 1. Generate portfolio (expensive, can be done in background)
8
+ # 2. Sample from portfolio (fast, can be done publicly)
9
+ class CreatePanelPortfolios < ActiveRecord::Migration[7.0]
10
+ def change
11
+ create_table :decidim_stratified_sortitions_panel_portfolios do |t|
12
+ t.references :decidim_stratified_sortitions_stratified_sortition,
13
+ null: false,
14
+ foreign_key: { to_table: :decidim_stratified_sortitions_stratified_sortitions },
15
+ index: { name: "idx_panel_portfolios_on_sortition_id" }
16
+
17
+ # Panel data (JSON arrays)
18
+ # panels: [[id1, id2, ...], [id3, id4, ...], ...]
19
+ # probabilities: [0.3, 0.25, ...]
20
+ # selection_probabilities: { participant_id => probability, ... }
21
+ t.jsonb :panels, null: false, default: []
22
+ t.jsonb :probabilities, null: false, default: []
23
+ t.jsonb :selection_probabilities, null: false, default: {}
24
+
25
+ # Generation metadata
26
+ t.datetime :generated_at, null: false
27
+ t.float :generation_time_seconds
28
+ t.integer :num_iterations
29
+ t.boolean :convergence_achieved, null: false, default: false
30
+
31
+ # Sampling result (null until sampled)
32
+ t.integer :selected_panel_index
33
+ t.datetime :selected_at
34
+ t.string :verification_seed
35
+ t.float :random_value_used
36
+
37
+ t.timestamps
38
+ end
39
+
40
+ # Ensure only one portfolio per sortition
41
+ add_index :decidim_stratified_sortitions_panel_portfolios,
42
+ :decidim_stratified_sortitions_stratified_sortition_id,
43
+ unique: true,
44
+ name: "idx_panel_portfolios_unique_sortition"
45
+ end
46
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddForeignKeyToStratifiedSortitionsComponent < ActiveRecord::Migration[5.2]
4
+ def change
5
+ add_foreign_key :decidim_stratified_sortitions_stratified_sortitions,
6
+ :decidim_components,
7
+ column: :decidim_component_id,
8
+ on_delete: :cascade
9
+ end
10
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddCascadeToStratifiedSortitionsForeignKeys < ActiveRecord::Migration[7.0]
4
+ def change
5
+ add_foreign_key :decidim_stratified_sortitions_strata,
6
+ :decidim_stratified_sortitions_stratified_sortitions,
7
+ column: :decidim_stratified_sortition_id,
8
+ on_delete: :cascade
9
+
10
+ add_foreign_key :decidim_stratified_sortitions_substrata,
11
+ :decidim_stratified_sortitions_strata,
12
+ column: :decidim_stratified_sortitions_stratum_id,
13
+ on_delete: :cascade
14
+
15
+ remove_foreign_key :decidim_stratified_sortitions_sample_imports,
16
+ column: :stratified_sortition_id
17
+ add_foreign_key :decidim_stratified_sortitions_sample_imports,
18
+ :decidim_stratified_sortitions_stratified_sortitions,
19
+ column: :stratified_sortition_id,
20
+ on_delete: :cascade
21
+
22
+ remove_foreign_key :decidim_stratified_sortitions_sample_participants,
23
+ column: :decidim_stratified_sortition_id
24
+ add_foreign_key :decidim_stratified_sortitions_sample_participants,
25
+ :decidim_stratified_sortitions_stratified_sortitions,
26
+ column: :decidim_stratified_sortition_id,
27
+ on_delete: :cascade
28
+
29
+ remove_foreign_key :decidim_stratified_sortitions_sample_participants,
30
+ column: :decidim_stratified_sortitions_sample_import_id
31
+ add_foreign_key :decidim_stratified_sortitions_sample_participants,
32
+ :decidim_stratified_sortitions_sample_imports,
33
+ column: :decidim_stratified_sortitions_sample_import_id,
34
+ on_delete: :cascade
35
+
36
+ remove_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
37
+ column: :decidim_stratified_sortitions_sample_participant_id
38
+ add_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
39
+ :decidim_stratified_sortitions_sample_participants,
40
+ column: :decidim_stratified_sortitions_sample_participant_id,
41
+ on_delete: :cascade
42
+
43
+ remove_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
44
+ column: :decidim_stratified_sortitions_stratum_id
45
+ add_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
46
+ :decidim_stratified_sortitions_strata,
47
+ column: :decidim_stratified_sortitions_stratum_id,
48
+ on_delete: :cascade
49
+
50
+ remove_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
51
+ column: :decidim_stratified_sortitions_substratum_id
52
+ add_foreign_key :decidim_stratified_sortitions_sample_participant_strata,
53
+ :decidim_stratified_sortitions_substrata,
54
+ column: :decidim_stratified_sortitions_substratum_id,
55
+ on_delete: :cascade
56
+
57
+ remove_foreign_key :decidim_stratified_sortitions_panel_portfolios,
58
+ column: :decidim_stratified_sortitions_stratified_sortition_id
59
+ add_foreign_key :decidim_stratified_sortitions_panel_portfolios,
60
+ :decidim_stratified_sortitions_stratified_sortitions,
61
+ column: :decidim_stratified_sortitions_stratified_sortition_id,
62
+ on_delete: :cascade
63
+ end
64
+ end