Encuestas de satisfacción de las capacitaciones
Que la encuesta que se toma después de cada capacitación le llegue a todos los que asistieron, que diga la verdad sobre quién ve las respuestas, y que sus números signifiquen lo que dicen: hoy abrir el enlace cuenta como respondió, una escala de 1 a 10 se guarda como 1 a 5, y la encuesta que se anuncia anónima guarda el nombre de cada uno.
1Lo que hay hoy
En el producto conviven seis sistemas de encuestas. El de las capacitaciones que se usa hoy es el de WebClass Trainer, en el repo Laravel:
| Pieza | Dónde |
|---|---|
| La aplicación | SPA Vue 2 en resources/js/components/trainer/*, servida por routes/web.php:623 (/trainer/home/{vue_capture?}) |
| El backend | app/Http/Controllers/Api/trainer/TrainerController.php (6.767 líneas); rutas en routes/api.php:228-375 |
| El formulario que responde el participante | pollComponent.vue |
| Crear la capacitación | modal «Nueva Capacitación» repetido en statusComponent.vue, dashboardFourComponent.vue y crmComponent.vue |
| Reportes | dashboardComponent.vue, statusLarav.vue + modalNotasDetail.vue; Excel en Exports/encuestasTraining.php |
| Build | webpack.mix.js:55 — resources/js/trainer/trainer.js → public/js. No hace falta una entrada nueva |
Los otros cinco no son de capacitaciones o están en desuso: el sistema de 2019 con
código de 5 caracteres (TrainingController, tablas trainings,
training_questions…), las encuestas globales y escolares del vanilla
(home/encuesta_global, home/encuesta), la del SIMCE y las de
comunicados. Este gadget toca sólo el del Trainer. No hay nada de SENCE ni de OTEC
en ninguno de los dos repos.
El flujo, tal como está construido
- En Curriculum se arma el catálogo: área → tema → sesión
(
area_poll_type→webclass_productos→poll_type), y cada sesión tiene su encuesta (poll→poll_question). - El capacitador crea la capacitación —colegio, sesión, capacitador,
fecha, presencial o remota con token de Setmore—: una fila de
training_result. - Asigna participantes: del colegio (
training_poll_alumnos) o externos, a mano o por Excel (training_poll_alumnos_externos). - Inicia y termina (
t_inicio,t_termino). Al terminar se genera un enlace corto, se copia al portapapeles, y un segundo después se manda correo sólo a los externos. - El participante abre el enlace y responde. Lo interno pasa por el login del vanilla; lo externo, por un JWT en la URL.
2Lo que está mal, relevado en el código
No es una lista de gustos: cada punto está en un archivo y una línea. El mecanismo de
los tres primeros se volvió a leer línea por línea antes de escribir esto
(created() → saveResult, el texto de anonimato, y
handleClick); el resto sale del mismo relevamiento y conviene confirmarlo al tocar
cada método.
Los números no dicen lo que parecen
Al cargar el formulario, pollComponent.vue:259 (created()) llama a
_updateResult(), que hace POST /api/trainer/saveResult. Y
TrainerController::saveResult (líneas 84-99) crea la fila de
training_poll con el user_id y la IP —antes de que la
persona conteste nada—.
De esa fila dependen tres cosas, y las tres quedan mal:
- El conteo:
contestada = IF(tp.training_result_id IS NULL,0,1)(≈:4291). - El certificado: lista a todos los de
training_poll, o sea a todos los que abrieron (informeCapacitaciones.vue,:2758). - La edición:
guardarEditarCalendariose bloquea en cuanto alguien abre (:4658).
pollComponent.vue:13: «sus respuestas son necesarias para nuestra mejora continua
y son de carácter anónimo». Pero se guardan user_id e
user_ip, y los reportes muestran nombre, RUT, correo y foto de cada respondente.
Es una promesa incumplida hecha por escrito, y es lo más urgente de todo el
documento: se arregla cumpliéndola o dejando de hacerla.
La última pregunta se muestra con botones del 1 al 10 (pollComponent.vue:136-169),
pero handleClick (:450-503) la reduce: 1-2→1, 3-4→2, …
9-10→5. Un 9 y un 10 quedan iguales; un 8 queda igual que un 7. La persona eligió entre
diez y se guardó entre cinco. Además eso sólo pasa si la encuesta tiene 4
preguntas o más (:279): con menos, la última queda en 0 y el formulario
no se puede enviar nunca.
- Reenviar infla los conteos:
training_respondse sobrescribe, peroquestion_trainer_response(la pregunta Sí/No) se inserta cada vez (:402). - El promedio de los externos une
training_respond_externocontraining_poll_alumnos_externos.iden vez detraining_poll_externos.id(:4329), y losSUMsobre variosLEFT JOINse multiplican. - Las preguntas ocultas se muestran:
getPollno filtrapoll_question.visible(:221-223). - Las preguntas nuevas no tienen grupo:
crearPreguntano escribepoll_group_id, así que no aparecen en el modal por grupos. - La IP guardada es la de Google:
getInfoconsulta siempreipinfo.io/lite/8.8.8.8.training_ip,_city,_regiony_orgson columnas muertas en la práctica. - El Excel no trae a los externos: el SQL de
detalleCapacitacionExcelno une sus tablas.
A quién no le llega
- A los internos no se les envía nada. El correo (
EnviarCorreoEncuestaEx→Mail/EncuestaExterna.php) es sólo parainterna = 2; al resto, el capacitador les pasa el enlace a mano. - El externo puede abrir el enlace una sola vez.
verificarTokenJwTcrea su fila en la primera visita; en la segunda, si ya existe, lo manda awww.webclass.com(:5418-5444). Si cerró sin enviar, perdió la encuesta. - El correo se manda con un
setTimeout(1000)sin esperar a que exista el enlace (statusComponent.vue:1637-1641): puede salir sin link. - Y nadie verifica la asistencia:
estadose marca pero no condiciona nada; el correo externo sale a todos los asignados.
Seguridad
- El grupo
Route::group(['prefix' => 'trainer'])(routes/api.php:228) no tiene middleware de autenticación, ycreatePolltoma eliduserdel request: cualquiera puede responder a nombre de otro. - El hash del enlace corto es
max(id)+1(:2170): secuencial, se adivina probando el siguiente. El JWT del externo no tiene vencimiento. - SQL concatenado en al menos ocho métodos (
guardarDificultad,getCoberturaCapacitacion,getCrmPolls…), un token de ipinfo escrito enmethods.js, y todos loscatchdevuelven la traza al cliente.
3Quién lo usa
- El capacitador de WebClass: crea la capacitación, carga a los participantes, toma asistencia, la termina. Lo ve en el computador.
- El participante: un docente, un UTP, un director. Puede tener cuenta en WebClass o no (colegio temporal, invitado). Responde desde el teléfono, casi siempre al salir de la sesión: el formulario se diseña para eso.
- La coordinación de capacitación, que lee los resultados.
4Dónde vive
En el Trainer, sin módulo nuevo: el detalle de la capacitación reemplaza a los
tres modales duplicados de «Nueva Capacitación» y a «Mis capacitaciones»
(statusComponent.vue, hoy comentado en menuTrainer.vue:42 pero accesible
por ruta). El formulario reemplaza a pollComponent.vue. Van en el bundle que ya
existe (webpack.mix.js:55).
Una sola ruta de respuesta para internos y externos, con el token de la invitación
(§8) y sin login. Hoy son dos caminos —el interno pasa por el login del vanilla y un
auto-POST a /loginFromWebclass; el externo por JWT—, con dos pares de tablas y
el reporte Excel mirando sólo uno. El login no agrega seguridad sobre un token al azar de
una persona, y le cuesta la respuesta a quien no recuerda su clave en el teléfono.
5Pantalla 1 — Asignar (el capacitador)
La lista
Las capacitaciones con su sesión, colegio, fecha, capacitador, estado (programada · en curso · terminada) y, en una columna propia, «6 de 10 respondieron» con su barra. Es lo que el capacitador necesita saber de un vistazo: si tiene que insistir.
El detalle
Participantes
- Se agregan del colegio (con cuenta), pegando desde Excel, o a mano como externos. En un colegio temporal no hay «del colegio».
- Pegar desde Excel se revisa antes de agregar, fila por fila: correo mal escrito, sin nombre, ya estaba en la lista, repetido en lo pegado. Cada error es una persona a la que nunca le llegaría la encuesta, y hoy nadie se entera. Acepta cualquier orden de columnas: busca el correo, el rol y el nombre.
- Asistencia: se marca al iniciar —todos marcados, se desmarca a quien no vino—. Quien llegó tarde se marca después, y si la encuesta ya salió aparece «Enviar ahora» en su fila.
- Cada fila dice el estado de su encuesta, con hora: sin enviar · enviada · abrió, sin responder · respondió, y cuántos recordatorios le llegaron. Son cuatro estados y sólo el último es «respondió».
La encuesta
- Las preguntas son las de la sesión y se ven aquí, agrupadas y con su tipo; se editan en Curriculum, no aquí.
- ¿Quién ve cada respuesta? — anónima (promedios y comentarios, nunca quién dijo qué; de cada persona sólo se sabe si respondió) o con nombre. El participante lee exactamente lo que se eligió. Una vez enviada no se puede cambiar: sería romper lo que se le prometió a quien ya respondió.
- ¿Quién la recibe? — sólo quienes asistieron (lo normal), o todos los inscritos, para cuando no se tomó asistencia. También se bloquea al enviar.
- Hasta cuándo se puede responder (3, 7 o 14 días después de terminar), recordatorio automático a los 2 días a quien no haya respondido, y si se entrega el certificado al responder (ver §12).
El envío
- «Terminar y enviar la encuesta» pide confirmación diciendo a cuántas personas le llega, cuántas sin cuenta en WebClass, cuántas quedan fuera por no asistir, y que el anonimato ya no se podrá cambiar.
- Le llega a todos los destinatarios por correo, con cuenta o sin ella, cada uno con su enlace.
- «Recordar a N pendientes»: a quienes no la abrieron y a quienes la abrieron sin terminar. A quien ya respondió no le llega nada.
- «Proyectar el enlace», para que respondan en la sala antes de irse: una dirección corta (y en el producto, su QR). Al entrar cada uno se identifica con su correo y recibe su enlace: el enlace de la sala no es un formulario abierto, porque entonces cualquiera responde por otro y se cuenta dos veces.
6Pantalla 2 — Responder (el participante)
- El correo dice qué capacitación, cuándo, con quién, cuántas preguntas y cuánto toma, si es anónima, hasta cuándo se puede responder y que el enlace es personal.
- Abrir el enlace anota «abrió», nada más.
- Arriba de las preguntas, la promesa de privacidad que corresponde a lo configurado, en palabras simples. Si es anónima y entrega certificado, dice que de la persona sólo se sabe que respondió, para darle el certificado.
- Las preguntas por grupo (Organización, Contenidos y relevancia, Capacitador, Satisfacción general), numeradas, con una barra de avance fija arriba.
- Acuerdo de 1 a 5: cinco botones grandes para el pulgar, con los extremos rotulados y la elección dicha en palabras debajo («De acuerdo»).
- Escala de 0 a 10: once botones, y se guarda el número que se tocó.
- Sí o no: la pregunta general de hoy (
question_trainer_general, con[texto]reemplazado por la duración). Si responde «no», el comentario es obligatorio —como hoy—. - Texto libre opcional, con contador (500). Hoy tiene
requireden vuelidate perosubmitno lo revisa: en la práctica ya es opcional, y así se deja. - Lo que responde se guarda mientras avanza. Si cierra y vuelve a abrir el enlace, sigue donde iba.
- Enviar incompleta no se puede, y no con un «faltan preguntas» genérico: se marcan en rojo las que faltan, se dice cuántas, y se lleva a la primera.
- Al enviar, «¡Gracias, Paula!» y, si está configurado, su certificado individual, para imprimir o guardar en PDF.
- Se responde una vez. Volver a abrir el enlace muestra la misma pantalla de gracias.
- Pasada la fecha de cierre, el enlace dice que la encuesta cerró y hasta cuándo se podía.
7Resultados
En el mismo detalle de la capacitación, a la derecha:
- Cuántos respondieron de cuántos la recibieron, con el porcentaje, y una barra de tres tramos: respondieron · abrieron y no terminaron · no la han abierto. Con la nota explícita de que abrir no cuenta.
- Promedio por grupo de las preguntas de acuerdo, con un decimal. Hoy
_calculateAVGredondea a entero en el cliente, y 3,5 y 4,4 se leen igual. - La escala de 0 a 10: el promedio y el reparto en recomiendan (9-10) · neutros (7-8) · no recomiendan (0-6), con el índice (% que recomienda − % que no). Sólo se puede calcular porque ahora se guarda el número real.
- Sí / no de la duración, y los comentarios —los de «duró distinto» marcados—, sin nombre si es anónima.
- Si es anónima y hay menos de 3 respuestas, no se muestran: con una sola, el promedio es la respuesta de alguien, y en una capacitación de doce se sabe quién fue.
8El modelo de datos
Una tabla nueva: la invitación nueva
Es la pieza que falta. Hoy «a quién se le envió», «quién abrió» y «quién respondió» se deducen de la existencia de filas en tablas distintas, y por eso se confunden.
CREATE TABLE training_poll_invitacion (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
training_result_id INT UNSIGNED NOT NULL,
usuario_id INT UNSIGNED NULL, -- con cuenta en WebClass
usuario_externo_id INT UNSIGNED NULL, -- usuario_colegio_temporal
token CHAR(20) NOT NULL UNIQUE, -- al azar, de UNA persona
enviada_at DATETIME NULL,
abierta_at DATETIME NULL,
respondida_at DATETIME NULL,
recordatorios TINYINT UNSIGNED NOT NULL DEFAULT 0,
ultimo_recordatorio DATETIME NULL,
borrador JSON NULL, -- lo contestado a medias
CHECK ((usuario_id IS NULL) <> (usuario_externo_id IS NULL))
);
- Reemplaza al enlace corto de
urlscomo enlace personal, al JWT sin vencimiento, y a «existe una fila entraining_poll» como sinónimo de respondió. El token vence con la encuesta (cierra_at). - Une a internos y externos en una tabla: el correo, los recordatorios, los conteos y el certificado salen de aquí para los dos.
borradoresJSON: producción corre MySQL 8.4 (relevado para el gadget 01). Se borra al enviar.- El
CHECKse cumple de verdad en 8.4; en 5.7 se habría ignorado.
Columnas nuevas en training_result nuevas
| Columna | Qué guarda |
|---|---|
anonima TINYINT | Lo que se le prometió al participante. Inmutable desde enviada_at. |
destinatarios ENUM('asistentes','todos') | Quién la recibe. Inmutable desde enviada_at. |
enviada_at, cierra_at DATETIME | Cuándo salió y hasta cuándo se responde. |
recordatorio, certificado TINYINT | Las dos opciones. |
El tipo de pregunta, explícito nuevo
poll_question suma tipo ('acuerdo5', 'escala10',
'sino', 'texto'), obligatoria y orden, y
crearPregunta tiene que escribir poll_group_id. El formulario deja de
adivinar el tipo por la posición: hoy «la última es la de caritas», y eso rompe
cualquier encuesta de menos de 4 preguntas.
Cambios de comportamiento sobre lo que ya existe
training_pollse crea al ENVIAR, no al abrir.saveResultdeja de llamarse desdecreated().- Si es anónima,
training_poll.user_ideuser_ipvan enNULL. Que respondió queda sólo entraining_poll_invitacion.respondida_at, y no se guarda el vínculo entre la invitación y la respuesta. - Cuidado con las horas: si
training_pollguarda la hora exacta y la invitación también, se cruzan y el anonimato se pierde. En una anónima, la respuesta guarda sólo la fecha. training_respond.valueguarda el 0-10 tal cual para las de escala. Lo histórico ya está reducido a 1-5 y no se puede recuperar: los reportes tienen que distinguir antes y después del cambio, o el promedio mezcla dos escalas.- Una respuesta por invitación: se rechaza el segundo envío, también en
question_trainer_response. getPollfiltravisible = 1.
9Seguridad, lo mínimo
- Las rutas del capacitador, detrás de
authy del rol. Las del participante, sólo con el token de la invitación, y quién responde sale del token, nunca del request. - Tokens al azar (20 caracteres, alfabeto sin ambigüedades), que vencen con la encuesta.
- Todo SQL de lo que se toque, con parámetros. Los
catchno devuelven la traza. - El token de ipinfo sale del código, y la consulta a
8.8.8.8se elimina: no guarda nada útil. - Los permisos por id de usuario escritos en el código (
2603450,3650461…) pasan a rol.
10Fuera de alcance
- El editor de preguntas (Curriculum): sólo se le suma el tipo y el grupo.
- El CRM (vtiger): se mantiene lo que hace hoy, marcar la actividad como «Realizada».
- Los otros cinco sistemas de encuestas.
- Los reportes más allá del detalle de la capacitación. El Excel sí tiene que incluir a los externos, porque con la tabla de invitaciones ya no hay dos caminos.
- SENCE: no hay nada en el producto sobre qué construir.
11Criterios de aceptación
- Abrir el enlace y cerrarlo no suma una respuesta, no pone a nadie en el certificado y no bloquea la edición.
- Al terminar, la encuesta llega por correo a todos los destinatarios, con cuenta y sin ella, cada uno con su enlace.
- Quien no asistió no la recibe (si así está configurado); quien llegó tarde se marca y se le puede enviar.
- Pegar desde Excel marca, antes de agregar, los correos mal escritos, los repetidos y los que ya estaban.
- Cada participante muestra uno de cuatro estados —sin enviar, enviada, abrió sin responder, respondió— con su hora.
- El recordatorio no le llega a quien ya respondió.
- Una encuesta anónima no guarda quién dio cada respuesta, y el reporte no muestra nombres. Con menos de 3 respuestas no muestra resultados.
- El texto que lee el participante coincide con lo configurado.
- El anonimato y los destinatarios no se pueden cambiar después de enviar.
- Un 9 en la escala de 0 a 10 se guarda como 9.
- Cerrar el formulario a medias y volver a abrirlo conserva lo contestado.
- No se puede enviar incompleta; se marcan las preguntas que faltan. «No» en la duración exige comentario.
- Se responde una sola vez; el segundo envío se rechaza en el servidor, no sólo en la pantalla.
- Una encuesta de 3 preguntas se puede enviar.
- Nadie puede responder a nombre de otro cambiando un id en el request.
12Qué hay que decidir antes de programar
- ¿Anónima o con nombre, por defecto? La maqueta propone anónima, porque es lo que hoy se promete. Si el negocio necesita saber quién dijo qué —para llamar a un colegio descontento, por ejemplo—, la salida honesta es «con nombre», dicho así. Lo que no puede seguir es prometer lo primero y hacer lo segundo.
- ¿El certificado depende de responder la encuesta? Hoy sí, de hecho (y de abrirla). Recomendación: que dependa de la asistencia. Condicionarlo a responder presiona a contestar cualquier cosa para descargarlo, y sesga justo los números que se quieren medir. La maqueta lo entrega al terminar la encuesta porque es donde el participante está, pero el mismo certificado debería poder bajarse desde el correo aunque no responda.
- ¿Qué se hace con lo histórico de la escala? Está guardado de 1 a 5. O se muestra aparte, o se convierte a una escala común sabiendo que se perdió resolución.
- ¿Cuántos días por defecto? La maqueta usa 7.
- El proveedor de correo y su límite: hoy es la cola de Laravel
(
EnviarCorreoEncuestaEx). Mandarle a todos —no sólo a externos— multiplica el volumen. - Las preguntas reales de cada sesión: la maqueta usa textos verosímiles, no los de producción. Con el tipo explícito, hay que asignarle uno a cada pregunta existente.
Es HTML autónomo y no guarda nada ni envía correos: al recargar vuelve al estado inicial. Las dos vistas —capacitador y participante— comparten el estado, así que lo que se responde en el teléfono aparece en los resultados al volver a la vista 1. El reloj está fijo en el lunes 28 de septiembre a las 10:12 y avanza unos minutos con cada acción.
Atajos para una reunión: ?cap=1 abre la capacitación terminada
(con resultados), ?cap=2 la de hoy —para terminarla y enviar—,
?cap=3 la programada en un colegio temporal, y ?vista=participante
abre el teléfono.