cuestionario validado, alerta de respuestas faltantes, ocultar preguntas, v2 en reportes

This commit is contained in:
Your Name
2025-02-17 09:39:26 -06:00
parent 6365291fe4
commit 87bfddff5a
7 changed files with 4201 additions and 901 deletions
+26 -3
View File
@@ -14,6 +14,20 @@
</b-select>
</b-field>
<b-field label="Version:">
<b-select v-model="version" expanded>
<option value="" disabled>Seleccione una version:</option>
<option value="v1">v1</option>
<option value="v2">
v2
</option>
</b-select>
</b-field>
<b-field label="Año:">
<b-select v-model="selectedYear" expanded>
<option value="" disabled>Seleccione un año:</option>
@@ -51,6 +65,7 @@ export default {
return {
selectedCuestionario: '',
selectedYear: '',
version:''
}
},
methods: {
@@ -58,16 +73,24 @@ export default {
this.updateIsLoading(true)
axios
.get(
`${process.env.api}/${this.selectedCuestionario}?year=${this.selectedYear}`,
this.admin.token
`${process.env.api}/${this.selectedCuestionario}`, {
params: {
year: this.selectedYear,
version: this.version,
},
headers: {
Authorization: `Bearer ${this.admin.token}`,
},
}
)
.then((res) => {
fileDownload(
res.data,
`${this.selectedYear}_${this.selectedCuestionario}.csv`
`${this.selectedYear}_${this.selectedCuestionario}_${this.version}.csv`
)
this.selectedYear = ''
this.selectedCuestionario = ''
this.version = ''
this.updateIsLoading(false)
})
.catch((err) => {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,951 @@
<template>
<div class="container mt-5">
<!-- <h1 class="text-center mb-4">{{ formulario.titulo }}</h1>
<p class="text-center">{{ formulario.descripcion }}</p> -->
<div >
<div v-for="(item, index) in sortedItems" :key="item.id || item.idTabla" class="mb-4 mt-6">
<div v-if="item.tipo" class="mt-6">
<label :for="item.id" class="form-label">{{ item.numeroPregunta }}. {{ item.texto }}</label>
<div v-if="item.tipo === 'seleccionUnica'" :key="item.id">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<!-- :id="item.id" -->
<b-radio
v-model="respuestas[item.id]"
:key="item.id + '-' + opcion"
:native-value="opcion"
type="is-info"
@change="updateRespuestas(item.id, opcion)"
>
{{ opcion }}
</b-radio>
</div>
</div>
<div v-if="item.tipo === 'seleccionMultiple'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<input
class="checkb"
type="checkbox"
:value="opcion"
:checked="respuestas[item.id].includes(opcion)"
@change="toggleSelection(item.id, opcion, $event)"
/>
{{ opcion }}
</div>
</div>
<!-- v-model="respuestas[item.id]" -->
<div v-if="item.tipo === 'texto'">
<b-input
:id="item.id"
v-model="respuestas[item.id]"
placeholder="Escribe tu respuesta aquí"
></b-input>
</div>
</div>
<div v-else>
<!-- <h3 class="mb-3">{{ item.numeroPregunta }}. {{ item.preguntaTabla }}</h3>
<h5 v-if="item.subPreguntaTabla" class="mb-3">{{ item.subPreguntaTabla }}</h5>
-->
<template v-if="index === 0 || item.numeroPregunta !== sortedItems[index - 1].numeroPregunta">
<h3 class="mb-3">
{{ item.numeroPregunta }}. {{ item.preguntaTabla }}
</h3>
</template>
<!-- Subtítulo (h5) siempre que exista -->
<h5 v-if="item.subPreguntaTabla" class="mb-3">
{{ item.subPreguntaTabla }}
</h5>
<table class="table table-bordered text-center">
<thead>
<tr>
<th></th>
<th v-for="opcion in item.renglones[0].opciones" :key="opcion">{{ opcion }}</th>
</tr>
</thead>
<tbody>
<tr v-for="renglon in item.renglones" :key="renglon.idRenglon">
<td>{{ renglon.textoRenglon }}</td>
<td v-for="opcion in renglon.opciones" :key="opcion">
<input
type="radio"
:name="`tabla-${item.idTabla}-renglon-${renglon.idRenglon}`"
:value="opcion"
v-model="respuestas[item.idTabla][renglon.idRenglon]"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="my-6 mb-6 has-text-centered">
<b-field class="centro">
<button
class="button is-success is-medium"
@click="() => { submitForm() }"
:disabled="!isFormComplete"
>
Enviar
</button>
</b-field>
</div>
</div>
</template>
<script>
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'
import axios from 'axios'
export default {
data() {
return {
respuestas: {}, // Inicialización para almacenar las respuestas
formulario: {
titulo: '2025 Evaluación del Universitario(a)',
descripcion:
'Te invitamos a compartir tu experiencia respecto al programa de servicio social en el que participaste. Tus respuestas son confidenciales.',
preguntas: [
{
id: 'criterios',
tipo: 'seleccionMultiple',
numeroPregunta:1,
texto:
'Señala los tres principales criterios que utilizaste para seleccionar el programa de servicio social',
opciones: [
'Actividades adscritas al programa',
'Apoyo económico',
'Asesoría académica',
'Dependencia de la UNAM',
'Flexibilidad en horarios',
'Institución del sector público',
'Institución del sector social',
'Invitación de profesores',
'Modalidad de titulación por servicio social',
'Objetivo del programa',
'Oportunidad de desarrollo de tesis',
'Prestigio de la institución',
'Recomendación de compañeros',
],
},
{
numeroPregunta:2,
id: 'inscripcion',
tipo: 'seleccionUnica',
texto: '¿Cómo evalúas tu proceso de inscripción?',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
numeroPregunta:4,
id: 'actividades',
tipo: 'seleccionUnica',
texto: '¿Las actividades que realizaste fueron acordes a lo descrito en el programa?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:5,
id: 'actividadesComentarios2',
tipo: 'texto',
texto:
'En caso de que las actividades que realizaste hayan sido distintas a las descritas en el programa, enlistas ¿Cuáles fueron?',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
{
numeroPregunta:6,
id: 'espacioFisico',
tipo: 'seleccionUnica',
texto:
'¿Contaste con un espacio físico adecuado para realizar tus actividades?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:7,
id: 'instalaciones',
tipo: 'seleccionUnica',
texto: 'Las instalaciones en las que te desempeñaste, contaban con condiciones adecuadas para la realización de las actividades que te encomendaron',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:8,
id: 'instalacionesComentarios',
tipo: 'texto',
texto: 'Si respondiste NO, ¿Por qué?',
condicional: {
preguntaId: 'instalaciones',
valor: 'No',
},
},
{
numeroPregunta:9,
id: 'materialProporcionado',
tipo: 'seleccionUnica',
texto: '¿La institución te proporciono material/equipo necesario para el desempeño de tus actividades?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:13,
id: 'supervisor',
tipo: 'seleccionUnica',
texto: '¿Quién asesoró y supervisó las actividades que realizaste?',
opciones: [
'Coordinador administrativo del programa',
'Responsable directo del programa',
'Alguien diferente a los dos anteriores',
],
},
{
numeroPregunta:14,
id: 'apoyoEconomico',
tipo: 'seleccionUnica',
texto: 'El apoyo económico señalado en el programa...',
opciones: [
'Se te otorgó de acuerdo a lo establecido',
'Fue condicionado',
'Se te proporcionó solo una parte',
],
},
{
numeroPregunta:15,
id: 'recomendacion',
tipo: 'seleccionUnica',
texto: '¿Recomendarías el programa en que participaste?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:16,
id: 'actividadesComentarios3',
tipo: 'texto',
texto:
'¿Por qué?',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
{
numeroPregunta:18,
id: 'actividadesComentarios1',
tipo: 'texto',
texto:
'Enlista los conocimiento o habilidades que te ayudarían a un mejor desempeño para tu actividad profesional a futuro',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
],
tablas: [
{
idTabla: 130,
preguntaTabla: '¿En la entidad o programa donde realizaste tu servicio social recibiste…?',
subPreguntaTabla: '',
numeroPregunta: 3,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Asesoría',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Acompañamiento',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Supervisión',
opciones: ['si', 'no'],
},
{
idRenglon: 4,
textoRenglon: 'Seguimiento',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 104,
preguntaTabla: 'El trato que recibiste fue:',
subPreguntaTabla: '',
numeroPregunta: 10,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Profesional',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Amable',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Respetuoso',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 105,
preguntaTabla: 'En tu servicio social…',
subPreguntaTabla: '',
numeroPregunta: 11,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Aplicaste conocimientos',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 2,
textoRenglon: 'Desarrollaste habilidades',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 3,
textoRenglon: 'Enriqueciste conocimientos',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 4,
textoRenglon: 'Fortaleciste tus habilidades',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
],
},
{
idTabla: 106,
preguntaTabla: 'Con respecto a la plataforma SIASSyPP web.',
subPreguntaTabla: 'La información que te proporcionaron sobre los programas fue:',
numeroPregunta: 17,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Adecuada',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Suficiente',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Actualizada',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 101,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'A. Habilidades personales',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Puntualidad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Eficiencia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Organización',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Eficacia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 102,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'B. Habilidades profesionales ',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Disposición',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Proactivo/anticipación',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Propositivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Resolutivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 103,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'C. Habilidades sociales',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Empatía',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Trabajo en equipo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Comunicación asertiva',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Receptividad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Tolerancia a la frustación',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 1042,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'D. Conocimientos',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Conocimientos teóricos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Conocimientos metodológicos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Pensamiento critico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Formación académica general',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Uso tecnológico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
],
},
}
},
created() {
this.respuestas = this.respuestas || {};
this.formulario.preguntas.forEach((pregunta) => {
if (!(pregunta.id in this.respuestas)) { // Solo inicializa si no existe
if (pregunta.tipo === 'seleccionMultiple') {
this.$set(this.respuestas, pregunta.id, []);
} else {
this.$set(this.respuestas, pregunta.id, null); // Usa null en lugar de ''
}
}
});
this.formulario.tablas.forEach((tabla) => {
if (!(tabla.idTabla in this.respuestas)) {
this.$set(this.respuestas, tabla.idTabla, {});
}
tabla.renglones.forEach((renglon) => {
if (!(renglon.idRenglon in this.respuestas[tabla.idTabla])) {
this.$set(this.respuestas[tabla.idTabla], renglon.idRenglon, null);
}
});
});
},
watch: {
respuestas: {
handler(newVal) {
console.log("Estado actualizado de respuestas:", newVal);
},
deep: true
}
},
computed: {
sortedItems() {
const items = [...this.formulario.preguntas, ...this.formulario.tablas]
return items.sort((a, b) => {
if (a.numeroPregunta === b.numeroPregunta) {
return (a.idTabla || 0) - (b.idTabla || 0)
}
return a.numeroPregunta - b.numeroPregunta
})
},
isFormComplete() {
// Verifica todas las preguntas individuales
const allQuestionsAnswered = this.formulario.preguntas.every((pregunta) => {
const respuesta = this.respuestas[pregunta.id];
if (pregunta.tipo === "seleccionMultiple") {
return respuesta && respuesta.length > 0; // Debe tener al menos una opción seleccionada
}
return respuesta !== null && respuesta !== ""; // No debe ser null ni vacío
});
// Verifica todas las respuestas de la tabla
const allTablesAnswered = this.formulario.tablas.every((tabla) => {
return tabla.renglones.every((renglon) => {
return this.respuestas[tabla.idTabla] && this.respuestas[tabla.idTabla][renglon.idRenglon] !== null;
});
});
return allQuestionsAnswered && allTablesAnswered;
}
},
methods: {
toggleSelection(id, opcion, event) {
let selectedOptions = this.respuestas[id];
if (selectedOptions.includes(opcion)) {
// Si ya está seleccionada, se deselecciona
this.respuestas[id] = selectedOptions.filter(opt => opt !== opcion);
} else {
if (selectedOptions.length < 3) {
// Si hay espacio, se agrega la opción
this.respuestas[id].push(opcion);
} else {
// Evita la selección de más de 3 opciones
event.preventDefault();
this.$buefy.dialog.alert({
title: "Límite alcanzado",
message: "Solo puedes seleccionar hasta 3 opciones.",
type: "is-warning",
});
}
}
},
limitSelection(id, opcion, event) {
if (this.respuestas[id].length >= 3 && !this.respuestas[id].includes(opcion)) {
// Evita que se seleccione la opción adicional
event.preventDefault();
this.$buefy.dialog.alert({
title: "Límite alcanzado",
message: "Solo puedes seleccionar hasta 3 opciones.",
type: "is-warning",
});
}
},
updateRespuestas(id, valor) {
this.$set(this.respuestas, id, valor);
console.log("Radio seleccionado:", id, valor);
},
validateResponses() {
for (const pregunta of this.formulario.preguntas) {
const respuesta = this.respuestas[pregunta.id];
// Verifica que no haya respuestas vacías
if (respuesta === null || respuesta === "") {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, responde la pregunta: "${pregunta.texto}"`,
type: "is-danger",
});
return false;
}
// Para selección múltiple, al menos un elemento debe estar seleccionado
if (pregunta.tipo === "seleccionMultiple" && (!respuesta || respuesta.length === 0)) {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, selecciona al menos una opción en: "${pregunta.texto}"`,
type: "is-danger",
});
return false;
}
// Validación adicional si la pregunta es de texto (evitar respuestas peligrosas)
if (pregunta.tipo === "texto" && respuesta.length > 500) {
this.$buefy.dialog.alert({
title: "Error",
message: `La respuesta de la pregunta "${pregunta.texto}" es demasiado larga.`,
type: "is-danger",
});
return false;
}
}
// Verifica que todas las respuestas en las tablas estén completas
for (const tabla of this.formulario.tablas) {
for (const renglon of tabla.renglones) {
const respuesta = this.respuestas[tabla.idTabla]?.[renglon.idRenglon];
if (respuesta === null) {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, responde la pregunta en la tabla: "${tabla.preguntaTabla}" en la fila "${renglon.textoRenglon}"`,
type: "is-danger",
});
return false;
}
}
}
return true;
},
submitForm() {
if (!this.validateResponses()) return; // Detiene el envío si la validación falla
console.log('Respuestas a enviar:', this.respuestas)
//alert('Formulario enviado. Gracias por tu participación.')
// Aquí formateamos las respuestas
const respuestaFormateada = this.formatearRespuestas(this.respuestas)
console.log('Respuestas formateadas:', respuestaFormateada)
// this.sendAnswers(respuestaFormateada)
let data = { ...respuestaFormateada,
idServicio: localStorage.getItem('idServicio'), // 4,
p1: 'respuesta1 - trabajando para que sea una string',
}
axios
.post(`${process.env.api}/cuestionario_alumno`, data, //this.token
)
.then((res) => {
this.$buefy.dialog.alert({
title: 'Success',
message: 'Se enviaron los datos correctamente',
type: 'is-success',
hasIcon: true,
icon: 'checkbox-marked-circle',
ariaRole: 'alertdialog',
ariaModal: true,
})
//this.reset()
localStorage.removeItem('idCuestionarioAlumno')
this.$router.push('/alumno')
})
.catch((error) => {
// this.error(error.response.data.message)
this.$buefy.dialog.alert({
title: "Error",
message: error.response?.data?.message || "Error al enviar el formulario",
type: "is-danger",
});
})
.finally(() => {
this.isLoading = false
})
},
formatearRespuestas(respuestas) {
// Objeto final a devolver
const resultado = {}
// Recorremos todas las claves de "respuestas"
for (const key in respuestas) {
// Verificamos si es un ID de tabla (numérico) o un ID de pregunta (string)
const esTabla = !isNaN(Number(key)) // true si la clave se puede convertir a número
if (esTabla) {
// 1) BUSCAMOS LA DEFINICIÓN DE LA TABLA
const idTabla = parseInt(key, 10)
const tablaDef = this.formulario.tablas.find(t => t.idTabla === idTabla)
if (!tablaDef) {
// Si no se encontró la tabla, continuamos
continue
}
// 2) OBTENEMOS EL NÚMERO DE PREGUNTA Y, OPCIONALMENTE, LA LETRA (A, B, C...)
const numPregunta = tablaDef.numeroPregunta
// Si quieres “sacar” la letra (A, B, C…) de algo como "A. Habilidades sociales"
// Se puede hacer un match sencillo al principio
let subLetra = ''
if (tablaDef.subPreguntaTabla) {
// Ejemplo: "A. Habilidades personales"
const coincidencia = tablaDef.subPreguntaTabla.match(/^([A-E])\./)
if (coincidencia) {
subLetra = coincidencia[1] // 'A', 'B', 'C', etc.
}
}
// 3) LUEGO RECORREMOS LAS RESPUESTAS DE LA TABLA (por cada idRenglon)
const respuestasTabla = respuestas[key] // p.ej: { "1": "Deficiente", "2": "Básico", ... }
for (const idRenglon in respuestasTabla) {
const valor = respuestasTabla[idRenglon]
// Formamos la clave final.
// Si hay subLetra, hacemos `p3_A_1`. Sino, solo `p3_1`.
let clave = `p${numPregunta}_${idRenglon}`
if (subLetra) {
clave = `p${numPregunta}_${subLetra}_${idRenglon}`
}
resultado[clave] = valor
}
} else {
// NO ES UNA TABLA => ES UNA PREGUNTA NORMAL
// Buscamos su definición para obtener el numeroPregunta
const preguntaDef = this.formulario.preguntas.find(p => p.id === key)
if (!preguntaDef) {
// No se encontró la definición (caso raro)
continue
}
const numPregunta = preguntaDef.numeroPregunta
// Creamos algo como `p2`, `p5`, etc.
const clave = `p${numPregunta}`
resultado[clave] = respuestas[key]
}
}
return resultado
},
},
}
</script>
<style scoped></style>
<!-- respuestas de cuestionario formulario2
todas las preguntas se guardan correctamente
{
"p3_A_1": "Deficiente",
"p3_A_2": "Deficiente",
"p3_A_3": "Deficiente",
"p3_A_4": "Deficiente",
"p3_B_1": "Básico",
"p3_B_2": "Básico",
"p3_B_3": "Básico",
"p3_B_4": "Básico",
"p3_C_1": "Intermedio",
"p3_C_2": "Intermedio",
"p3_C_3": "Intermedio",
"p3_C_4": "Intermedio",
"p3_C_5": "Intermedio",
"p3_D_1": "Destacado",
"p3_D_2": "Destacado",
"p3_D_3": "Destacado",
"p3_D_4": "Destacado",
"p3_D_5": "Destacado",
"p3_E_1": "Deficiente",
"p1": "respuesta1",
"p2": "Excelente",
"p4": "respuesta4",
"p5": "Sí",
"p6": "respuesta 6"
} -->
<!-- respuestas de cuestionario formulario alumno
{
"p12_A_1": "Deficiente",
"p12_A_2": "Deficiente",
"p12_A_3": "Deficiente",
"p12_A_4": "Deficiente",
"p12_B_1": "Básico",
"p12_B_2": "Básico",
"p12_B_3": "Básico",
"p12_B_4": "Básico",
"p12_C_1": "Intermedio",
"p12_C_2": "Intermedio",
"p12_C_3": "Intermedio",
"p12_C_4": "Intermedio",
"p10_1": "si",
"p10_2": "si",
"p10_3": "si",
"p11_1": "Nunca",
"p11_2": "Rara vez",
"p11_3": "Algunas veces",
"p11_4": "Siempre",
"p17_1": "si",
"p17_2": "no",
"p17_3": "si",
"p3_1": "si",
"p3_2": "si",
"p3_3": "si",
"p3_4": "si",
"p12_D_1": "Destacado",
"p12_D_2": "Destacado",
"p12_D_3": "Destacado",
"p12_D_4": "Destacado",
"p12_D_5": "Destacado",
"p1": [
"Dependencia de la UNAM",
"Asesoría académica",
"Oportunidad de desarrollo de tesis"
],
"p2": "Deficiente",
"p4": "No",
"p5": "respuesta5",
"p6": "Sí",
"p7": "Sí",
"p8": "respuesta8",
"p9": "Sí",
"p13": "Responsable directo del programa",
"p14": "Se te otorgó de acuerdo a lo establecido",
"p15": "Sí",
"p16": "respuesta 16",
"p18": "respuesta18"
}
-->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,951 @@
<template>
<div class="container mt-5">
<!-- <h1 class="text-center mb-4">{{ formulario.titulo }}</h1>
<p class="text-center">{{ formulario.descripcion }}</p> -->
<div >
<div v-for="(item, index) in sortedItems" :key="item.id || item.idTabla" class="mb-4 mt-6">
<div v-if="item.tipo" class="mt-6">
<label :for="item.id" class="form-label">{{ item.numeroPregunta }}. {{ item.texto }}</label>
<div v-if="item.tipo === 'seleccionUnica'" :key="item.id">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<!-- :id="item.id" -->
<b-radio
v-model="respuestas[item.id]"
:key="item.id + '-' + opcion"
:native-value="opcion"
type="is-info"
@change="updateRespuestas(item.id, opcion)"
>
{{ opcion }}
</b-radio>
</div>
</div>
<div v-if="item.tipo === 'seleccionMultiple'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<input
class="checkb"
type="checkbox"
:value="opcion"
:checked="respuestas[item.id].includes(opcion)"
@change="toggleSelection(item.id, opcion, $event)"
/>
{{ opcion }}
</div>
</div>
<!-- v-model="respuestas[item.id]" -->
<div v-if="item.tipo === 'texto'">
<b-input
:id="item.id"
v-model="respuestas[item.id]"
placeholder="Escribe tu respuesta aquí"
></b-input>
</div>
</div>
<div v-else>
<!-- <h3 class="mb-3">{{ item.numeroPregunta }}. {{ item.preguntaTabla }}</h3>
<h5 v-if="item.subPreguntaTabla" class="mb-3">{{ item.subPreguntaTabla }}</h5>
-->
<template v-if="index === 0 || item.numeroPregunta !== sortedItems[index - 1].numeroPregunta">
<h3 class="mb-3">
{{ item.numeroPregunta }}. {{ item.preguntaTabla }}
</h3>
</template>
<!-- Subtítulo (h5) siempre que exista -->
<h5 v-if="item.subPreguntaTabla" class="mb-3">
{{ item.subPreguntaTabla }}
</h5>
<table class="table table-bordered text-center">
<thead>
<tr>
<th></th>
<th v-for="opcion in item.renglones[0].opciones" :key="opcion">{{ opcion }}</th>
</tr>
</thead>
<tbody>
<tr v-for="renglon in item.renglones" :key="renglon.idRenglon">
<td>{{ renglon.textoRenglon }}</td>
<td v-for="opcion in renglon.opciones" :key="opcion">
<input
type="radio"
:name="`tabla-${item.idTabla}-renglon-${renglon.idRenglon}`"
:value="opcion"
v-model="respuestas[item.idTabla][renglon.idRenglon]"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="my-6 mb-6" >
<b-field class="centro">
<button
class="button is-success is-medium"
@click="() => { submitForm() }"
:disabled="!isFormComplete"
>
Enviar
</button>
</b-field>
</div>
</div>
</template>
<script>
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'
import axios from 'axios'
export default {
data() {
return {
respuestas: {}, // Inicialización para almacenar las respuestas
formulario: {
titulo: '2025 Evaluación del Universitario(a)',
descripcion:
'Te invitamos a compartir tu experiencia respecto al programa de servicio social en el que participaste. Tus respuestas son confidenciales.',
preguntas: [
{
id: 'criterios',
tipo: 'seleccionMultiple',
numeroPregunta:1,
texto:
'Señala los tres principales criterios que utilizaste para seleccionar el programa de servicio social',
opciones: [
'Actividades adscritas al programa',
'Apoyo económico',
'Asesoría académica',
'Dependencia de la UNAM',
'Flexibilidad en horarios',
'Institución del sector público',
'Institución del sector social',
'Invitación de profesores',
'Modalidad de titulación por servicio social',
'Objetivo del programa',
'Oportunidad de desarrollo de tesis',
'Prestigio de la institución',
'Recomendación de compañeros',
],
},
{
numeroPregunta:2,
id: 'inscripcion',
tipo: 'seleccionUnica',
texto: '¿Cómo evalúas tu proceso de inscripción?',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
numeroPregunta:4,
id: 'actividades',
tipo: 'seleccionUnica',
texto: '¿Las actividades que realizaste fueron acordes a lo descrito en el programa?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:5,
id: 'actividadesComentarios2',
tipo: 'texto',
texto:
'En caso de que las actividades que realizaste hayan sido distintas a las descritas en el programa, enlistas ¿Cuáles fueron?',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
{
numeroPregunta:6,
id: 'espacioFisico',
tipo: 'seleccionUnica',
texto:
'¿Contaste con un espacio físico adecuado para realizar tus actividades?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:7,
id: 'instalaciones',
tipo: 'seleccionUnica',
texto: 'Las instalaciones en las que te desempeñaste, contaban con condiciones adecuadas para la realización de las actividades que te encomendaron',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:8,
id: 'instalacionesComentarios',
tipo: 'texto',
texto: 'Si respondiste NO, ¿Por qué?',
condicional: {
preguntaId: 'instalaciones',
valor: 'No',
},
},
{
numeroPregunta:9,
id: 'materialProporcionado',
tipo: 'seleccionUnica',
texto: '¿La institución te proporciono material/equipo necesario para el desempeño de tus actividades?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:13,
id: 'supervisor',
tipo: 'seleccionUnica',
texto: '¿Quién asesoró y supervisó las actividades que realizaste?',
opciones: [
'Coordinador administrativo del programa',
'Responsable directo del programa',
'Alguien diferente a los dos anteriores',
],
},
{
numeroPregunta:14,
id: 'apoyoEconomico',
tipo: 'seleccionUnica',
texto: 'El apoyo económico señalado en el programa...',
opciones: [
'Se te otorgó de acuerdo a lo establecido',
'Fue condicionado',
'Se te proporcionó solo una parte',
],
},
{
numeroPregunta:15,
id: 'recomendacion',
tipo: 'seleccionUnica',
texto: '¿Recomendarías el programa en que participaste?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta:16,
id: 'actividadesComentarios3',
tipo: 'texto',
texto:
'¿Por qué?',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
{
numeroPregunta:18,
id: 'actividadesComentarios1',
tipo: 'texto',
texto:
'Enlista los conocimiento o habilidades que te ayudarían a un mejor desempeño para tu actividad profesional a futuro',
condicional: {
preguntaId: 'actividades',
valor: 'No',
},
},
],
tablas: [
{
idTabla: 130,
preguntaTabla: '¿En la entidad o programa donde realizaste tu servicio social recibiste…?',
subPreguntaTabla: '',
numeroPregunta: 3,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Asesoría',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Acompañamiento',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Supervisión',
opciones: ['si', 'no'],
},
{
idRenglon: 4,
textoRenglon: 'Seguimiento',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 104,
preguntaTabla: 'El trato que recibiste fue:',
subPreguntaTabla: '',
numeroPregunta: 10,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Profesional',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Amable',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Respetuoso',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 105,
preguntaTabla: 'En tu servicio social…',
subPreguntaTabla: '',
numeroPregunta: 11,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Aplicaste conocimientos',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 2,
textoRenglon: 'Desarrollaste habilidades',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 3,
textoRenglon: 'Enriqueciste conocimientos',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
{
idRenglon: 4,
textoRenglon: 'Fortaleciste tus habilidades',
opciones: ['Nunca', 'Rara vez', 'Algunas veces','Regularmente', 'Siempre'],
},
],
},
{
idTabla: 106,
preguntaTabla: 'Con respecto a la plataforma SIASSyPP web.',
subPreguntaTabla: 'La información que te proporcionaron sobre los programas fue:',
numeroPregunta: 17,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Adecuada',
opciones: ['si', 'no'],
},
{
idRenglon: 2,
textoRenglon: 'Suficiente',
opciones: ['si', 'no'],
},
{
idRenglon: 3,
textoRenglon: 'Actualizada',
opciones: ['si', 'no'],
},
],
},
{
idTabla: 101,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'A. Habilidades personales',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Puntualidad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Eficiencia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Organización',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Eficacia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 102,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'B. Habilidades profesionales ',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Disposición',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Proactivo/anticipación',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Propositivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Resolutivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 103,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'C. Habilidades sociales',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Empatía',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Trabajo en equipo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Comunicación asertiva',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Receptividad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Tolerancia a la frustación',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 1042,
preguntaTabla: 'Evalúa tu propio desempeño en el servicio social en relación con',
subPreguntaTabla: 'D. Conocimientos',
numeroPregunta: 12,
renglones: [
{
idRenglon: 1,
textoRenglon: 'Conocimientos teóricos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Conocimientos metodológicos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Pensamiento critico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Formación académica general',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Uso tecnológico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
],
},
}
},
created() {
this.respuestas = this.respuestas || {};
this.formulario.preguntas.forEach((pregunta) => {
if (!(pregunta.id in this.respuestas)) { // Solo inicializa si no existe
if (pregunta.tipo === 'seleccionMultiple') {
this.$set(this.respuestas, pregunta.id, []);
} else {
this.$set(this.respuestas, pregunta.id, null); // Usa null en lugar de ''
}
}
});
this.formulario.tablas.forEach((tabla) => {
if (!(tabla.idTabla in this.respuestas)) {
this.$set(this.respuestas, tabla.idTabla, {});
}
tabla.renglones.forEach((renglon) => {
if (!(renglon.idRenglon in this.respuestas[tabla.idTabla])) {
this.$set(this.respuestas[tabla.idTabla], renglon.idRenglon, null);
}
});
});
},
watch: {
respuestas: {
handler(newVal) {
console.log("Estado actualizado de respuestas:", newVal);
},
deep: true
}
},
computed: {
sortedItems() {
const items = [...this.formulario.preguntas, ...this.formulario.tablas]
return items.sort((a, b) => {
if (a.numeroPregunta === b.numeroPregunta) {
return (a.idTabla || 0) - (b.idTabla || 0)
}
return a.numeroPregunta - b.numeroPregunta
})
},
isFormComplete() {
// Verifica todas las preguntas individuales
const allQuestionsAnswered = this.formulario.preguntas.every((pregunta) => {
const respuesta = this.respuestas[pregunta.id];
if (pregunta.tipo === "seleccionMultiple") {
return respuesta && respuesta.length > 0; // Debe tener al menos una opción seleccionada
}
return respuesta !== null && respuesta !== ""; // No debe ser null ni vacío
});
// Verifica todas las respuestas de la tabla
const allTablesAnswered = this.formulario.tablas.every((tabla) => {
return tabla.renglones.every((renglon) => {
return this.respuestas[tabla.idTabla] && this.respuestas[tabla.idTabla][renglon.idRenglon] !== null;
});
});
return allQuestionsAnswered && allTablesAnswered;
}
},
methods: {
toggleSelection(id, opcion, event) {
let selectedOptions = this.respuestas[id];
if (selectedOptions.includes(opcion)) {
// Si ya está seleccionada, se deselecciona
this.respuestas[id] = selectedOptions.filter(opt => opt !== opcion);
} else {
if (selectedOptions.length < 3) {
// Si hay espacio, se agrega la opción
this.respuestas[id].push(opcion);
} else {
// Evita la selección de más de 3 opciones
event.preventDefault();
this.$buefy.dialog.alert({
title: "Límite alcanzado",
message: "Solo puedes seleccionar hasta 3 opciones.",
type: "is-warning",
});
}
}
},
limitSelection(id, opcion, event) {
if (this.respuestas[id].length >= 3 && !this.respuestas[id].includes(opcion)) {
// Evita que se seleccione la opción adicional
event.preventDefault();
this.$buefy.dialog.alert({
title: "Límite alcanzado",
message: "Solo puedes seleccionar hasta 3 opciones.",
type: "is-warning",
});
}
},
updateRespuestas(id, valor) {
this.$set(this.respuestas, id, valor);
console.log("Radio seleccionado:", id, valor);
},
validateResponses() {
for (const pregunta of this.formulario.preguntas) {
const respuesta = this.respuestas[pregunta.id];
// Verifica que no haya respuestas vacías
if (respuesta === null || respuesta === "") {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, responde la pregunta: "${pregunta.texto}"`,
type: "is-danger",
});
return false;
}
// Para selección múltiple, al menos un elemento debe estar seleccionado
if (pregunta.tipo === "seleccionMultiple" && (!respuesta || respuesta.length === 0)) {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, selecciona al menos una opción en: "${pregunta.texto}"`,
type: "is-danger",
});
return false;
}
// Validación adicional si la pregunta es de texto (evitar respuestas peligrosas)
if (pregunta.tipo === "texto" && respuesta.length > 500) {
this.$buefy.dialog.alert({
title: "Error",
message: `La respuesta de la pregunta "${pregunta.texto}" es demasiado larga.`,
type: "is-danger",
});
return false;
}
}
// Verifica que todas las respuestas en las tablas estén completas
for (const tabla of this.formulario.tablas) {
for (const renglon of tabla.renglones) {
const respuesta = this.respuestas[tabla.idTabla]?.[renglon.idRenglon];
if (respuesta === null) {
this.$buefy.dialog.alert({
title: "Error",
message: `Por favor, responde la pregunta en la tabla: "${tabla.preguntaTabla}" en la fila "${renglon.textoRenglon}"`,
type: "is-danger",
});
return false;
}
}
}
return true;
},
submitForm() {
if (!this.validateResponses()) return; // Detiene el envío si la validación falla
console.log('Respuestas a enviar:', this.respuestas)
//alert('Formulario enviado. Gracias por tu participación.')
// Aquí formateamos las respuestas
const respuestaFormateada = this.formatearRespuestas(this.respuestas)
console.log('Respuestas formateadas:', respuestaFormateada)
// this.sendAnswers(respuestaFormateada)
let data = { ...respuestaFormateada,
idServicio: localStorage.getItem('idServicio'), // 4,
p1: 'respuesta1 - trabajando para que sea una string',
}
axios
.post(`${process.env.api}/cuestionario_alumno`, data, //this.token
)
.then((res) => {
this.$buefy.dialog.alert({
title: 'Success',
message: 'Se enviaron los datos correctamente',
type: 'is-success',
hasIcon: true,
icon: 'checkbox-marked-circle',
ariaRole: 'alertdialog',
ariaModal: true,
})
//this.reset()
localStorage.removeItem('idCuestionarioAlumno')
this.$router.push('/alumno')
})
.catch((error) => {
// this.error(error.response.data.message)
this.$buefy.dialog.alert({
title: "Error",
message: error.response?.data?.message || "Error al enviar el formulario",
type: "is-danger",
});
})
.finally(() => {
this.isLoading = false
})
},
formatearRespuestas(respuestas) {
// Objeto final a devolver
const resultado = {}
// Recorremos todas las claves de "respuestas"
for (const key in respuestas) {
// Verificamos si es un ID de tabla (numérico) o un ID de pregunta (string)
const esTabla = !isNaN(Number(key)) // true si la clave se puede convertir a número
if (esTabla) {
// 1) BUSCAMOS LA DEFINICIÓN DE LA TABLA
const idTabla = parseInt(key, 10)
const tablaDef = this.formulario.tablas.find(t => t.idTabla === idTabla)
if (!tablaDef) {
// Si no se encontró la tabla, continuamos
continue
}
// 2) OBTENEMOS EL NÚMERO DE PREGUNTA Y, OPCIONALMENTE, LA LETRA (A, B, C...)
const numPregunta = tablaDef.numeroPregunta
// Si quieres “sacar” la letra (A, B, C…) de algo como "A. Habilidades sociales"
// Se puede hacer un match sencillo al principio
let subLetra = ''
if (tablaDef.subPreguntaTabla) {
// Ejemplo: "A. Habilidades personales"
const coincidencia = tablaDef.subPreguntaTabla.match(/^([A-E])\./)
if (coincidencia) {
subLetra = coincidencia[1] // 'A', 'B', 'C', etc.
}
}
// 3) LUEGO RECORREMOS LAS RESPUESTAS DE LA TABLA (por cada idRenglon)
const respuestasTabla = respuestas[key] // p.ej: { "1": "Deficiente", "2": "Básico", ... }
for (const idRenglon in respuestasTabla) {
const valor = respuestasTabla[idRenglon]
// Formamos la clave final.
// Si hay subLetra, hacemos `p3_A_1`. Sino, solo `p3_1`.
let clave = `p${numPregunta}_${idRenglon}`
if (subLetra) {
clave = `p${numPregunta}_${subLetra}_${idRenglon}`
}
resultado[clave] = valor
}
} else {
// NO ES UNA TABLA => ES UNA PREGUNTA NORMAL
// Buscamos su definición para obtener el numeroPregunta
const preguntaDef = this.formulario.preguntas.find(p => p.id === key)
if (!preguntaDef) {
// No se encontró la definición (caso raro)
continue
}
const numPregunta = preguntaDef.numeroPregunta
// Creamos algo como `p2`, `p5`, etc.
const clave = `p${numPregunta}`
resultado[clave] = respuestas[key]
}
}
return resultado
},
},
}
</script>
<!-- respuestas de cuestionario formulario2
todas las preguntas se guardan correctamente
{
"p3_A_1": "Deficiente",
"p3_A_2": "Deficiente",
"p3_A_3": "Deficiente",
"p3_A_4": "Deficiente",
"p3_B_1": "Básico",
"p3_B_2": "Básico",
"p3_B_3": "Básico",
"p3_B_4": "Básico",
"p3_C_1": "Intermedio",
"p3_C_2": "Intermedio",
"p3_C_3": "Intermedio",
"p3_C_4": "Intermedio",
"p3_C_5": "Intermedio",
"p3_D_1": "Destacado",
"p3_D_2": "Destacado",
"p3_D_3": "Destacado",
"p3_D_4": "Destacado",
"p3_D_5": "Destacado",
"p3_E_1": "Deficiente",
"p1": "respuesta1",
"p2": "Excelente",
"p4": "respuesta4",
"p5": "Sí",
"p6": "respuesta 6"
} -->
<!-- respuestas de cuestionario formulario alumno
{
"p12_A_1": "Deficiente",
"p12_A_2": "Deficiente",
"p12_A_3": "Deficiente",
"p12_A_4": "Deficiente",
"p12_B_1": "Básico",
"p12_B_2": "Básico",
"p12_B_3": "Básico",
"p12_B_4": "Básico",
"p12_C_1": "Intermedio",
"p12_C_2": "Intermedio",
"p12_C_3": "Intermedio",
"p12_C_4": "Intermedio",
"p10_1": "si",
"p10_2": "si",
"p10_3": "si",
"p11_1": "Nunca",
"p11_2": "Rara vez",
"p11_3": "Algunas veces",
"p11_4": "Siempre",
"p17_1": "si",
"p17_2": "no",
"p17_3": "si",
"p3_1": "si",
"p3_2": "si",
"p3_3": "si",
"p3_4": "si",
"p12_D_1": "Destacado",
"p12_D_2": "Destacado",
"p12_D_3": "Destacado",
"p12_D_4": "Destacado",
"p12_D_5": "Destacado",
"p1": [
"Dependencia de la UNAM",
"Asesoría académica",
"Oportunidad de desarrollo de tesis"
],
"p2": "Deficiente",
"p4": "No",
"p5": "respuesta5",
"p6": "Sí",
"p7": "Sí",
"p8": "respuesta8",
"p9": "Sí",
"p13": "Responsable directo del programa",
"p14": "Se te otorgó de acuerdo a lo establecido",
"p15": "Sí",
"p16": "respuesta 16",
"p18": "respuesta18"
}
-->
@@ -1,19 +1,35 @@
<template>
<div class="container mt-5">
<!--
<h1 class="text-center mb-4">{{ formulario.titulo }}</h1>
<p class="text-center">{{ formulario.descripcion }}</p> -->
<!-- Notificación flotante sin botón de cierre -->
<b-notification
v-if="pendingItems.length && !isMobile"
type="is-warning"
class="notification-floating"
aria-close-label="Not applicable"
:closable="false"
>
<p><strong>Te faltan contestar:</strong></p>
<ul>
<li v-for="(item, idx) in pendingItems" :key="idx">
{{ item }}
</li>
</ul>
</b-notification>
<div>
<div
v-for="(item, index) in sortedItems"
:key="item.id || item.idTabla"
class="mb-6"
v-if="isItemVisible(item)"
>
<!-- PREGUNTAS (texto, selección única, selección múltiple) -->
<div v-if="item.tipo" class="mt-6">
<label :for="item.id" class="form-label"
>{{ item.numeroPregunta }}. {{ item.texto }}</label
>
<label :for="item.id" class="form-label">
{{ item.numeroPregunta }}. {{ item.texto }}
</label>
<!-- Selección Única -->
<div v-if="item.tipo === 'seleccionUnica'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<b-radio
@@ -26,6 +42,8 @@
</b-radio>
</div>
</div>
<!-- Selección Múltiple -->
<div v-if="item.tipo === 'seleccionMultiple'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<input
@@ -34,23 +52,25 @@
:value="opcion"
v-model="respuestas[item.id]"
/>
{{ opcion }}
</div>
</div>
<!-- Texto -->
<div v-if="item.tipo === 'texto'">
<b-input
:id="item.id"
v-model="respuestas[item.id]"
:maxlength="item.limite"
show-counter
placeholder="Escribe tu respuesta aquí"
></b-input>
</div>
</div>
<div v-else>
<!-- <h3 class="mb-3">{{ item.numeroPregunta }}. {{ item.preguntaTabla }}</h3>
<h5 v-if="item.subPreguntaTabla" class="mb-3">{{ item.subPreguntaTabla }}</h5>
-->
<!-- TABLAS -->
<div class="table-responsive" v-else>
<template
v-if="
index === 0 ||
@@ -62,7 +82,6 @@
</h3>
</template>
<!-- Subtítulo (h5) siempre que exista -->
<h5 v-if="item.subPreguntaTabla" class="mb-3">
{{ item.subPreguntaTabla }}
</h5>
@@ -94,11 +113,12 @@
</div>
</div>
<!-- BOTÓN ENVIAR -->
<div class="my-6 mb-6">
<b-field class="centro">
<button
class="button is-success is-medium"
@click="() => {submitForm()}"
@click="submitForm"
:disabled="!isFormComplete"
>
Enviar
@@ -116,7 +136,8 @@ import axios from 'axios'
export default {
data() {
return {
respuestas: {}, // Inicialización para almacenar las respuestas
isLoading: false,
respuestas: {},
formulario: {
titulo:
@@ -125,7 +146,6 @@ export default {
Te solicitamos llenar cuidadosamente los campos solicitados a continuación para poder
validar el término del servicio social de nuestro alumno(a).
Los datos están sujetos al aviso de privacidad integral que se puede consultar en el sitio web: www.acatlan.unam.mx/normatividad`,
preguntas: [
@@ -133,23 +153,25 @@ export default {
numeroPregunta: 1,
id: 'actividadesUniversitario',
tipo: 'texto',
limite: 100,
texto:
'Mencione las principales actividades que realizó el/la universitario(a) en su programa de servicio social',
'Mencione las principales actividades que realizó el/la universitario(a) en su programa de servicio social',
},
{
numeroPregunta: 2,
id: 'calificacionGeneral',
tipo: 'seleccionUnica',
texto:
'Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
'Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
opciones: ['Excelente', 'Bueno', 'Regular', 'Deficiente'],
},
{
numeroPregunta: 4,
id: 'cursosComplementarios',
tipo: 'texto',
limite: 100,
texto:
'¿Qué cursos considera que complementarían la formación de nuestros egresados?',
'¿Qué cursos considera que complementarían la formación de nuestros egresados?',
},
{
numeroPregunta: 5,
@@ -162,6 +184,7 @@ export default {
numeroPregunta: 6,
id: 'porqueNoContrataria',
tipo: 'texto',
limite: 100,
texto:
'Si su respuesta a la pregunta anterior fue NO, comente ¿por qué?',
condicional: {
@@ -172,7 +195,7 @@ export default {
],
tablas: [
// ----- PREGUNTA 3, dividida en sub-secciones A, B, C, D, E ------
// ----- PREGUNTA 3, dividida en sub-secciones A, B, C, D ------
{
idTabla: 301,
numeroPregunta: 3,
@@ -299,64 +322,22 @@ export default {
},
],
},
/* {
idTabla: 305,
numeroPregunta: 3,
preguntaTabla:
'3. Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
subPreguntaTabla: 'E. Otro',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Otro',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
}, */
],
},
}
},
/* created() {
// Inicializa las respuestas vacías para cada pregunta
this.formulario.preguntas.forEach((pregunta) => {
if (pregunta.tipo === 'evaluacion') {
this.respuestas[pregunta.id] = {}
pregunta.categorias.forEach((categoria) => {
categoria.items.forEach((item) => {
this.respuestas[pregunta.id][item] = ''
})
})
} else if (
pregunta.tipo === 'checkbox' ||
pregunta.tipo === 'seleccionMultiple'
) {
this.respuestas[pregunta.id] = []
} else {
this.respuestas[pregunta.id] = ''
}
})
// Inicializa respuestas para cada tabla
this.formulario.tablas.forEach((tabla) => {
this.$set(this.respuestas, tabla.idTabla, {}); // Asegura que exista respuestas[tabla.idTabla]
tabla.renglones.forEach((renglon) => {
this.$set(this.respuestas[tabla.idTabla], renglon.idRenglon, null); // Asegura que exista respuestas[tabla.idTabla][renglon.idRenglon]
});
});
}, */
created() {
// Inicializa las respuestas de cada pregunta
this.formulario.preguntas.forEach((pregunta) => {
if (pregunta.tipo === 'seleccionMultiple') {
this.$set(this.respuestas, pregunta.id, []) // Inicializar correctamente selección múltiple
this.$set(this.respuestas, pregunta.id, [])
} else {
this.$set(this.respuestas, pregunta.id, '') // Otras preguntas como texto o selección única
this.$set(this.respuestas, pregunta.id, '')
}
})
// Inicializa respuestas para cada tabla
// Inicializa las respuestas para cada tabla
this.formulario.tablas.forEach((tabla) => {
this.$set(this.respuestas, tabla.idTabla, {})
tabla.renglones.forEach((renglon) => {
@@ -366,6 +347,14 @@ export default {
},
computed: {
isMobile() {
return window.innerWidth < 576
},
// Combina preguntas y tablas en un array ordenado
sortedItems() {
const items = [...this.formulario.preguntas, ...this.formulario.tablas]
return items.sort((a, b) => {
@@ -376,58 +365,78 @@ export default {
})
},
isFormComplete() {
// Verifica todas las preguntas individuales
const allQuestionsAnswered = this.formulario.preguntas.every(
(pregunta) => {
const respuesta = this.respuestas[pregunta.id]
if (pregunta.tipo === 'seleccionMultiple') {
return respuesta && respuesta.length > 0 // Debe tener al menos una opción seleccionada
}
return respuesta !== null && respuesta !== '' // No debe ser null ni vacío
}
)
// Determina los ítems (preguntas/tablas) que siguen pendientes
pendingItems() {
let missing = []
// Verifica todas las respuestas de la tabla
const allTablesAnswered = this.formulario.tablas.every((tabla) => {
return tabla.renglones.every((renglon) => {
return (
this.respuestas[tabla.idTabla] &&
this.respuestas[tabla.idTabla][renglon.idRenglon] !== null
)
})
// Revisar preguntas (solo las que estén visibles)
this.formulario.preguntas.forEach((p) => {
if (!this.isItemVisible(p)) return
const answer = this.respuestas[p.id]
let answered = false
if (p.tipo === 'seleccionMultiple') {
answered = answer && answer.length > 0
} else {
answered = answer !== null && answer !== ''
}
if (!answered) {
missing.push(`Pregunta ${p.numeroPregunta}`)
}
})
return allQuestionsAnswered && allTablesAnswered
// Revisar tablas
this.formulario.tablas.forEach((t) => {
let allAnswered = t.renglones.every(
(r) =>
this.respuestas[t.idTabla] &&
this.respuestas[t.idTabla][r.idRenglon] !== null
)
if (!allAnswered) {
missing.push(`Tabla ${t.numeroPregunta}`)
}
})
return missing
},
// El formulario se considera completo si no hay ítems pendientes
isFormComplete() {
return this.pendingItems.length === 0
},
},
methods: {
// Determina si un item (pregunta/tabla) debe mostrarse (condicional)
isItemVisible(item) {
if (item.condicional) {
// Checa la respuesta de la pregunta 'preguntaId'
const respuestaCond = this.respuestas[item.condicional.preguntaId]
return respuestaCond === item.condicional.valor
}
return true
},
submitForm() {
if (!this.validateResponses()) {
return // Detiene el envío si la validación falla
return
}
console.log('Respuestas enviadas:', this.respuestas)
//alert('Formulario enviado. Gracias por tu participación.')
// Aquí formateamos las respuestas
// Convertir a formato p1, p2, p3_A_1, etc.
const respuestaFormateada = this.formatearRespuestas(this.respuestas)
console.log('Respuestas formateadas:', respuestaFormateada)
// this.sendAnswers(respuestaFormateada)
let data = {
...respuestaFormateada,
idServicio: localStorage.getItem('idServicio'),
p1: 'respuesta1 - trabajando para que sea una string',
}
this.isLoading = true
axios
.post(
`${process.env.api}/cuestionario_programa`,
data //this.token
)
.then((res) => {
.post(`${process.env.api}/cuestionario_programa`, data)
.then(() => {
this.$buefy.dialog.alert({
title: 'Success',
message: 'Se enviaron los datos correctamente',
@@ -437,14 +446,10 @@ export default {
ariaRole: 'alertdialog',
ariaModal: true,
})
//this.reset()
localStorage.removeItem('idCuestionarioAlumno')
this.$router.push('/alumno')
})
.catch((error) => {
// this.error(error.response.data.message)
this.$buefy.dialog.alert({
title: 'Error',
message:
@@ -457,123 +462,54 @@ export default {
})
},
formatearRespuestas(respuestas) {
// Objeto final a devolver
const resultado = {}
// Recorremos todas las claves de "respuestas"
for (const key in respuestas) {
// Verificamos si es un ID de tabla (numérico) o un ID de pregunta (string)
const esTabla = !isNaN(Number(key)) // true si la clave se puede convertir a número
if (esTabla) {
// 1) BUSCAMOS LA DEFINICIÓN DE LA TABLA
const idTabla = parseInt(key, 10)
const tablaDef = this.formulario.tablas.find(
(t) => t.idTabla === idTabla
)
if (!tablaDef) {
// Si no se encontró la tabla, continuamos
continue
}
// 2) OBTENEMOS EL NÚMERO DE PREGUNTA Y, OPCIONALMENTE, LA LETRA (A, B, C...)
const numPregunta = tablaDef.numeroPregunta
// Si quieres “sacar” la letra (A, B, C…) de algo como "A. Habilidades sociales"
// Se puede hacer un match sencillo al principio
let subLetra = ''
if (tablaDef.subPreguntaTabla) {
// Ejemplo: "A. Habilidades personales"
const coincidencia = tablaDef.subPreguntaTabla.match(/^([A-E])\./)
if (coincidencia) {
subLetra = coincidencia[1] // 'A', 'B', 'C', etc.
}
}
// 3) LUEGO RECORREMOS LAS RESPUESTAS DE LA TABLA (por cada idRenglon)
const respuestasTabla = respuestas[key] // p.ej: { "1": "Deficiente", "2": "Básico", ... }
for (const idRenglon in respuestasTabla) {
const valor = respuestasTabla[idRenglon]
// Formamos la clave final.
// Si hay subLetra, hacemos `p3_A_1`. Sino, solo `p3_1`.
let clave = `p${numPregunta}_${idRenglon}`
if (subLetra) {
clave = `p${numPregunta}_${subLetra}_${idRenglon}`
}
resultado[clave] = valor
}
} else {
// NO ES UNA TABLA => ES UNA PREGUNTA NORMAL
// Buscamos su definición para obtener el numeroPregunta
const preguntaDef = this.formulario.preguntas.find(
(p) => p.id === key
)
if (!preguntaDef) {
// No se encontró la definición (caso raro)
continue
}
const numPregunta = preguntaDef.numeroPregunta
// Creamos algo como `p2`, `p5`, etc.
const clave = `p${numPregunta}`
resultado[clave] = respuestas[key]
}
}
return resultado
},
// Valida que las preguntas y tablas visibles estén contestadas
validateResponses() {
// Validar preguntas
for (const pregunta of this.formulario.preguntas) {
if (!this.isItemVisible(pregunta)) continue
const respuesta = this.respuestas[pregunta.id]
// Verifica que no haya respuestas vacías
if (respuesta === null || respuesta === '') {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, responde la pregunta: "${pregunta.texto}"`,
type: 'is-danger',
})
return false
// Verifica que no esté vacía
if (pregunta.tipo === 'seleccionMultiple') {
if (!respuesta || respuesta.length === 0) {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, selecciona al menos una opción en la pregunta ${pregunta.numeroPregunta}.`,
type: 'is-danger',
})
return false
}
} else {
if (respuesta === null || respuesta === '') {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, responde la pregunta ${pregunta.numeroPregunta}.`,
type: 'is-danger',
})
return false
}
}
// Para selección múltiple, al menos un elemento debe estar seleccionado
if (
pregunta.tipo === 'seleccionMultiple' &&
(!respuesta || respuesta.length === 0)
) {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, selecciona al menos una opción en: "${pregunta.texto}"`,
type: 'is-danger',
})
return false
}
// Validación adicional si la pregunta es de texto (evitar respuestas peligrosas)
// Si es texto y excede 500 caracteres
if (pregunta.tipo === 'texto' && respuesta.length > 500) {
this.$buefy.dialog.alert({
title: 'Error',
message: `La respuesta de la pregunta "${pregunta.texto}" es demasiado larga.`,
message: `La respuesta de la pregunta ${pregunta.numeroPregunta} es demasiado larga (máx. 500 caracteres).`,
type: 'is-danger',
})
return false
}
}
// Verifica que todas las respuestas en las tablas estén completas
// Validar tablas
for (const tabla of this.formulario.tablas) {
for (const renglon of tabla.renglones) {
const respuesta = this.respuestas[tabla.idTabla]?.[renglon.idRenglon]
if (respuesta === null) {
const respRenglon = this.respuestas[tabla.idTabla]?.[renglon.idRenglon]
if (respRenglon === null) {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, responde la pregunta en la tabla: "${tabla.preguntaTabla}" en la fila "${renglon.textoRenglon}"`,
message: `Por favor, responde la tabla ${tabla.numeroPregunta}, fila "${renglon.textoRenglon}".`,
type: 'is-danger',
})
return false
@@ -584,96 +520,82 @@ export default {
return true
},
updateRespuestas(id, valor) {
this.$set(this.respuestas, id, valor)
console.log('Radio seleccionado:', id, valor)
// Convierte las respuestas en formato { p1: ..., p3_A_1: ..., etc. }
formatearRespuestas(respuestas) {
const resultado = {}
for (const key in respuestas) {
const esTabla = !isNaN(Number(key))
if (esTabla) {
// Es una tabla
const idTabla = parseInt(key, 10)
const tablaDef = this.formulario.tablas.find(
(t) => t.idTabla === idTabla
)
if (!tablaDef) continue
const numPregunta = tablaDef.numeroPregunta
let subLetra = ''
if (tablaDef.subPreguntaTabla) {
const match = tablaDef.subPreguntaTabla.match(/^([A-E])\./)
if (match) subLetra = match[1]
}
const respuestasTabla = respuestas[key]
for (const idRenglon in respuestasTabla) {
const valor = respuestasTabla[idRenglon]
let clave = `p${numPregunta}_${idRenglon}`
if (subLetra) {
clave = `p${numPregunta}_${subLetra}_${idRenglon}`
}
resultado[clave] = valor
}
} else {
// Es una pregunta normal
const preguntaDef = this.formulario.preguntas.find(
(p) => p.id === key
)
if (!preguntaDef) continue
const numPregunta = preguntaDef.numeroPregunta
const clave = `p${numPregunta}`
resultado[clave] = respuestas[key]
}
}
return resultado
},
},
}
</script>
<!-- respuestas de cuestionario formulario2
todas las preguntas se guardan correctamente
{
"p3_A_1": "Deficiente",
"p3_A_2": "Deficiente",
"p3_A_3": "Deficiente",
"p3_A_4": "Deficiente",
"p3_B_1": "Básico",
"p3_B_2": "Básico",
"p3_B_3": "Básico",
"p3_B_4": "Básico",
"p3_C_1": "Intermedio",
"p3_C_2": "Intermedio",
"p3_C_3": "Intermedio",
"p3_C_4": "Intermedio",
"p3_C_5": "Intermedio",
"p3_D_1": "Destacado",
"p3_D_2": "Destacado",
"p3_D_3": "Destacado",
"p3_D_4": "Destacado",
"p3_D_5": "Destacado",
"p3_E_1": "Deficiente",
"p1": "respuesta1",
"p2": "Excelente",
"p4": "respuesta4",
"p5": "Sí",
"p6": "respuesta 6"
} -->
<style scoped>
/* Notificación flotante en parte inferior derecha, sin botón de cerrar */
.notification-floating {
position: fixed !important;
bottom: 2rem;
right: 2rem;
z-index: 9999;
max-width: 350px;
width: auto; /* o 90% si prefieres */
box-sizing: border-box;
}
<!-- respuestas de cuestionario formulario alumno
{
"p12_A_1": "Deficiente",
"p12_A_2": "Deficiente",
"p12_A_3": "Deficiente",
"p12_A_4": "Deficiente",
"p12_B_1": "Básico",
"p12_B_2": "Básico",
"p12_B_3": "Básico",
"p12_B_4": "Básico",
"p12_C_1": "Intermedio",
"p12_C_2": "Intermedio",
"p12_C_3": "Intermedio",
"p12_C_4": "Intermedio",
"p10_1": "si",
"p10_2": "si",
"p10_3": "si",
"p11_1": "Nunca",
"p11_2": "Rara vez",
"p11_3": "Algunas veces",
"p11_4": "Siempre",
"p17_1": "si",
"p17_2": "no",
"p17_3": "si",
"p3_1": "si",
"p3_2": "si",
"p3_3": "si",
"p3_4": "si",
"p12_D_1": "Destacado",
"p12_D_2": "Destacado",
"p12_D_3": "Destacado",
"p12_D_4": "Destacado",
"p12_D_5": "Destacado",
"p1": [
"Dependencia de la UNAM",
"Asesoría académica",
"Oportunidad de desarrollo de tesis"
],
"p2": "Deficiente",
"p4": "No",
"p5": "respuesta5",
"p6": "Sí",
"p7": "Sí",
"p8": "respuesta8",
"p9": "Sí",
"p13": "Responsable directo del programa",
"p14": "Se te otorgó de acuerdo a lo establecido",
"p15": "Sí",
"p16": "respuesta 16",
"p18": "respuesta18"
/* Ajustes en pantallas pequeñas (ej. < 480px) */
@media (max-width: 480px) {
.notification-floating {
right: 1rem;
left: 1rem; /* Opcional: para centrar la notificación */
bottom: 1rem;
max-width: none; /* Para que no se limite a 350px */
width: auto; /* O 90%, como prefieras */
}
-->
}
.table-responsive {
width: 100%;
overflow-x: auto;
}
</style>
@@ -0,0 +1,679 @@
<template>
<div class="container mt-5">
<!--
<h1 class="text-center mb-4">{{ formulario.titulo }}</h1>
<p class="text-center">{{ formulario.descripcion }}</p> -->
<div>
<div
v-for="(item, index) in sortedItems"
:key="item.id || item.idTabla"
class="mb-6"
>
<div v-if="item.tipo" class="mt-6">
<label :for="item.id" class="form-label"
>{{ item.numeroPregunta }}. {{ item.texto }}</label
>
<div v-if="item.tipo === 'seleccionUnica'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<b-radio
v-model="respuestas[item.id]"
:id="item.id"
:native-value="opcion"
type="is-info"
>
{{ opcion }}
</b-radio>
</div>
</div>
<div v-if="item.tipo === 'seleccionMultiple'">
<div v-for="opcion in item.opciones" :key="opcion" class="SINO">
<input
class="checkb"
type="checkbox"
:value="opcion"
v-model="respuestas[item.id]"
/>
{{ opcion }}
</div>
</div>
<div v-if="item.tipo === 'texto'">
<b-input
:id="item.id"
v-model="respuestas[item.id]"
placeholder="Escribe tu respuesta aquí"
></b-input>
</div>
</div>
<div v-else>
<!-- <h3 class="mb-3">{{ item.numeroPregunta }}. {{ item.preguntaTabla }}</h3>
<h5 v-if="item.subPreguntaTabla" class="mb-3">{{ item.subPreguntaTabla }}</h5>
-->
<template
v-if="
index === 0 ||
item.numeroPregunta !== sortedItems[index - 1].numeroPregunta
"
>
<h3 class="mb-3">
{{ item.numeroPregunta }}. {{ item.preguntaTabla }}
</h3>
</template>
<!-- Subtítulo (h5) siempre que exista -->
<h5 v-if="item.subPreguntaTabla" class="mb-3">
{{ item.subPreguntaTabla }}
</h5>
<table class="table table-bordered text-center">
<thead>
<tr>
<th></th>
<th v-for="opcion in item.renglones[0].opciones" :key="opcion">
{{ opcion }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="renglon in item.renglones" :key="renglon.idRenglon">
<td>{{ renglon.textoRenglon }}</td>
<td v-for="opcion in renglon.opciones" :key="opcion">
<input
type="radio"
:name="`tabla-${item.idTabla}-renglon-${renglon.idRenglon}`"
:value="opcion"
v-model="respuestas[item.idTabla][renglon.idRenglon]"
/>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="my-6 mb-6">
<b-field class="centro">
<button
class="button is-success is-medium"
@click="() => {submitForm()}"
:disabled="!isFormComplete"
>
Enviar
</button>
</b-field>
</div>
</div>
</template>
<script>
import Buefy from 'buefy'
import 'buefy/dist/buefy.css'
import axios from 'axios'
export default {
data() {
return {
respuestas: {}, // Inicialización para almacenar las respuestas
formulario: {
titulo:
'2025 Evaluación de la institución receptora de servicio social',
descripcion: `Estimado(a) responsable de programa de servicio social:
Te solicitamos llenar cuidadosamente los campos solicitados a continuación para poder
validar el término del servicio social de nuestro alumno(a).
Los datos están sujetos al aviso de privacidad integral que se puede consultar en el sitio web: www.acatlan.unam.mx/normatividad`,
preguntas: [
{
numeroPregunta: 1,
id: 'actividadesUniversitario',
tipo: 'texto',
texto:
'Mencione las principales actividades que realizó el/la universitario(a) en su programa de servicio social',
},
{
numeroPregunta: 2,
id: 'calificacionGeneral',
tipo: 'seleccionUnica',
texto:
'Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
opciones: ['Excelente', 'Bueno', 'Regular', 'Deficiente'],
},
{
numeroPregunta: 4,
id: 'cursosComplementarios',
tipo: 'texto',
texto:
'¿Qué cursos considera que complementarían la formación de nuestros egresados?',
},
{
numeroPregunta: 5,
id: 'contratariaFuturo',
tipo: 'seleccionUnica',
texto: '¿Considera contratar en un futuro al universitario(a)?',
opciones: ['Sí', 'No'],
},
{
numeroPregunta: 6,
id: 'porqueNoContrataria',
tipo: 'texto',
texto:
'Si su respuesta a la pregunta anterior fue NO, comente ¿por qué?',
condicional: {
preguntaId: 'contratariaFuturo',
valor: 'No',
},
},
],
tablas: [
// ----- PREGUNTA 3, dividida en sub-secciones A, B, C, D, E ------
{
idTabla: 301,
numeroPregunta: 3,
preguntaTabla:
'Haciendo una evaluación general, ¿Cómo Califica el desempeño del universitario(a)?',
subPreguntaTabla: 'A. Habilidades personales',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Puntualidad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Eficiencia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Organizado',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Eficacia',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 302,
numeroPregunta: 3,
preguntaTabla:
'3. Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
subPreguntaTabla: 'B. Habilidades profesionales',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Disposición',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Proactivo/anticipación',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Propositivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Resolutivo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 303,
numeroPregunta: 3,
preguntaTabla:
'3. Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
subPreguntaTabla: 'C. Habilidades sociales',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Empatía',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Trabajo en equipo',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Comunicación asertiva',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Receptividad',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Tolerancia a la frustración',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
{
idTabla: 304,
numeroPregunta: 3,
preguntaTabla:
'3. Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
subPreguntaTabla: 'D. Conocimientos',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Conocimientos teóricos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 2,
textoRenglon: 'Conocimientos metodológicos',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 3,
textoRenglon: 'Pensamiento crítico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 4,
textoRenglon: 'Formación académica general',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
{
idRenglon: 5,
textoRenglon: 'Uso tecnológico',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
},
/* {
idTabla: 305,
numeroPregunta: 3,
preguntaTabla:
'3. Haciendo una evaluación general, ¿Cómo califica el desempeño del universitario(a)?',
subPreguntaTabla: 'E. Otro',
renglones: [
{
idRenglon: 1,
textoRenglon: 'Otro',
opciones: ['Deficiente', 'Básico', 'Intermedio', 'Destacado'],
},
],
}, */
],
},
}
},
/* created() {
// Inicializa las respuestas vacías para cada pregunta
this.formulario.preguntas.forEach((pregunta) => {
if (pregunta.tipo === 'evaluacion') {
this.respuestas[pregunta.id] = {}
pregunta.categorias.forEach((categoria) => {
categoria.items.forEach((item) => {
this.respuestas[pregunta.id][item] = ''
})
})
} else if (
pregunta.tipo === 'checkbox' ||
pregunta.tipo === 'seleccionMultiple'
) {
this.respuestas[pregunta.id] = []
} else {
this.respuestas[pregunta.id] = ''
}
})
// Inicializa respuestas para cada tabla
this.formulario.tablas.forEach((tabla) => {
this.$set(this.respuestas, tabla.idTabla, {}); // Asegura que exista respuestas[tabla.idTabla]
tabla.renglones.forEach((renglon) => {
this.$set(this.respuestas[tabla.idTabla], renglon.idRenglon, null); // Asegura que exista respuestas[tabla.idTabla][renglon.idRenglon]
});
});
}, */
created() {
this.formulario.preguntas.forEach((pregunta) => {
if (pregunta.tipo === 'seleccionMultiple') {
this.$set(this.respuestas, pregunta.id, []) // Inicializar correctamente selección múltiple
} else {
this.$set(this.respuestas, pregunta.id, '') // Otras preguntas como texto o selección única
}
})
// Inicializa respuestas para cada tabla
this.formulario.tablas.forEach((tabla) => {
this.$set(this.respuestas, tabla.idTabla, {})
tabla.renglones.forEach((renglon) => {
this.$set(this.respuestas[tabla.idTabla], renglon.idRenglon, null)
})
})
},
computed: {
sortedItems() {
const items = [...this.formulario.preguntas, ...this.formulario.tablas]
return items.sort((a, b) => {
if (a.numeroPregunta === b.numeroPregunta) {
return (a.idTabla || 0) - (b.idTabla || 0)
}
return a.numeroPregunta - b.numeroPregunta
})
},
isFormComplete() {
// Verifica todas las preguntas individuales
const allQuestionsAnswered = this.formulario.preguntas.every(
(pregunta) => {
const respuesta = this.respuestas[pregunta.id]
if (pregunta.tipo === 'seleccionMultiple') {
return respuesta && respuesta.length > 0 // Debe tener al menos una opción seleccionada
}
return respuesta !== null && respuesta !== '' // No debe ser null ni vacío
}
)
// Verifica todas las respuestas de la tabla
const allTablesAnswered = this.formulario.tablas.every((tabla) => {
return tabla.renglones.every((renglon) => {
return (
this.respuestas[tabla.idTabla] &&
this.respuestas[tabla.idTabla][renglon.idRenglon] !== null
)
})
})
return allQuestionsAnswered && allTablesAnswered
},
},
methods: {
submitForm() {
if (!this.validateResponses()) {
return // Detiene el envío si la validación falla
}
console.log('Respuestas enviadas:', this.respuestas)
//alert('Formulario enviado. Gracias por tu participación.')
// Aquí formateamos las respuestas
const respuestaFormateada = this.formatearRespuestas(this.respuestas)
console.log('Respuestas formateadas:', respuestaFormateada)
// this.sendAnswers(respuestaFormateada)
let data = {
...respuestaFormateada,
idServicio: localStorage.getItem('idServicio'),
p1: 'respuesta1 - trabajando para que sea una string',
}
axios
.post(
`${process.env.api}/cuestionario_programa`,
data //this.token
)
.then((res) => {
this.$buefy.dialog.alert({
title: 'Success',
message: 'Se enviaron los datos correctamente',
type: 'is-success',
hasIcon: true,
icon: 'checkbox-marked-circle',
ariaRole: 'alertdialog',
ariaModal: true,
})
//this.reset()
localStorage.removeItem('idCuestionarioAlumno')
this.$router.push('/alumno')
})
.catch((error) => {
// this.error(error.response.data.message)
this.$buefy.dialog.alert({
title: 'Error',
message:
error.response?.data?.message || 'Error al enviar el formulario',
type: 'is-danger',
})
})
.finally(() => {
this.isLoading = false
})
},
formatearRespuestas(respuestas) {
// Objeto final a devolver
const resultado = {}
// Recorremos todas las claves de "respuestas"
for (const key in respuestas) {
// Verificamos si es un ID de tabla (numérico) o un ID de pregunta (string)
const esTabla = !isNaN(Number(key)) // true si la clave se puede convertir a número
if (esTabla) {
// 1) BUSCAMOS LA DEFINICIÓN DE LA TABLA
const idTabla = parseInt(key, 10)
const tablaDef = this.formulario.tablas.find(
(t) => t.idTabla === idTabla
)
if (!tablaDef) {
// Si no se encontró la tabla, continuamos
continue
}
// 2) OBTENEMOS EL NÚMERO DE PREGUNTA Y, OPCIONALMENTE, LA LETRA (A, B, C...)
const numPregunta = tablaDef.numeroPregunta
// Si quieres “sacar” la letra (A, B, C…) de algo como "A. Habilidades sociales"
// Se puede hacer un match sencillo al principio
let subLetra = ''
if (tablaDef.subPreguntaTabla) {
// Ejemplo: "A. Habilidades personales"
const coincidencia = tablaDef.subPreguntaTabla.match(/^([A-E])\./)
if (coincidencia) {
subLetra = coincidencia[1] // 'A', 'B', 'C', etc.
}
}
// 3) LUEGO RECORREMOS LAS RESPUESTAS DE LA TABLA (por cada idRenglon)
const respuestasTabla = respuestas[key] // p.ej: { "1": "Deficiente", "2": "Básico", ... }
for (const idRenglon in respuestasTabla) {
const valor = respuestasTabla[idRenglon]
// Formamos la clave final.
// Si hay subLetra, hacemos `p3_A_1`. Sino, solo `p3_1`.
let clave = `p${numPregunta}_${idRenglon}`
if (subLetra) {
clave = `p${numPregunta}_${subLetra}_${idRenglon}`
}
resultado[clave] = valor
}
} else {
// NO ES UNA TABLA => ES UNA PREGUNTA NORMAL
// Buscamos su definición para obtener el numeroPregunta
const preguntaDef = this.formulario.preguntas.find(
(p) => p.id === key
)
if (!preguntaDef) {
// No se encontró la definición (caso raro)
continue
}
const numPregunta = preguntaDef.numeroPregunta
// Creamos algo como `p2`, `p5`, etc.
const clave = `p${numPregunta}`
resultado[clave] = respuestas[key]
}
}
return resultado
},
validateResponses() {
for (const pregunta of this.formulario.preguntas) {
const respuesta = this.respuestas[pregunta.id]
// Verifica que no haya respuestas vacías
if (respuesta === null || respuesta === '') {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, responde la pregunta: "${pregunta.texto}"`,
type: 'is-danger',
})
return false
}
// Para selección múltiple, al menos un elemento debe estar seleccionado
if (
pregunta.tipo === 'seleccionMultiple' &&
(!respuesta || respuesta.length === 0)
) {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, selecciona al menos una opción en: "${pregunta.texto}"`,
type: 'is-danger',
})
return false
}
// Validación adicional si la pregunta es de texto (evitar respuestas peligrosas)
if (pregunta.tipo === 'texto' && respuesta.length > 500) {
this.$buefy.dialog.alert({
title: 'Error',
message: `La respuesta de la pregunta "${pregunta.texto}" es demasiado larga.`,
type: 'is-danger',
})
return false
}
}
// Verifica que todas las respuestas en las tablas estén completas
for (const tabla of this.formulario.tablas) {
for (const renglon of tabla.renglones) {
const respuesta = this.respuestas[tabla.idTabla]?.[renglon.idRenglon]
if (respuesta === null) {
this.$buefy.dialog.alert({
title: 'Error',
message: `Por favor, responde la pregunta en la tabla: "${tabla.preguntaTabla}" en la fila "${renglon.textoRenglon}"`,
type: 'is-danger',
})
return false
}
}
}
return true
},
updateRespuestas(id, valor) {
this.$set(this.respuestas, id, valor)
console.log('Radio seleccionado:', id, valor)
},
},
}
</script>
<!-- respuestas de cuestionario formulario2
todas las preguntas se guardan correctamente
{
"p3_A_1": "Deficiente",
"p3_A_2": "Deficiente",
"p3_A_3": "Deficiente",
"p3_A_4": "Deficiente",
"p3_B_1": "Básico",
"p3_B_2": "Básico",
"p3_B_3": "Básico",
"p3_B_4": "Básico",
"p3_C_1": "Intermedio",
"p3_C_2": "Intermedio",
"p3_C_3": "Intermedio",
"p3_C_4": "Intermedio",
"p3_C_5": "Intermedio",
"p3_D_1": "Destacado",
"p3_D_2": "Destacado",
"p3_D_3": "Destacado",
"p3_D_4": "Destacado",
"p3_D_5": "Destacado",
"p3_E_1": "Deficiente",
"p1": "respuesta1",
"p2": "Excelente",
"p4": "respuesta4",
"p5": "Sí",
"p6": "respuesta 6"
} -->
<!-- respuestas de cuestionario formulario alumno
{
"p12_A_1": "Deficiente",
"p12_A_2": "Deficiente",
"p12_A_3": "Deficiente",
"p12_A_4": "Deficiente",
"p12_B_1": "Básico",
"p12_B_2": "Básico",
"p12_B_3": "Básico",
"p12_B_4": "Básico",
"p12_C_1": "Intermedio",
"p12_C_2": "Intermedio",
"p12_C_3": "Intermedio",
"p12_C_4": "Intermedio",
"p10_1": "si",
"p10_2": "si",
"p10_3": "si",
"p11_1": "Nunca",
"p11_2": "Rara vez",
"p11_3": "Algunas veces",
"p11_4": "Siempre",
"p17_1": "si",
"p17_2": "no",
"p17_3": "si",
"p3_1": "si",
"p3_2": "si",
"p3_3": "si",
"p3_4": "si",
"p12_D_1": "Destacado",
"p12_D_2": "Destacado",
"p12_D_3": "Destacado",
"p12_D_4": "Destacado",
"p12_D_5": "Destacado",
"p1": [
"Dependencia de la UNAM",
"Asesoría académica",
"Oportunidad de desarrollo de tesis"
],
"p2": "Deficiente",
"p4": "No",
"p5": "respuesta5",
"p6": "Sí",
"p7": "Sí",
"p8": "respuesta8",
"p9": "Sí",
"p13": "Responsable directo del programa",
"p14": "Se te otorgó de acuerdo a lo establecido",
"p15": "Sí",
"p16": "respuesta 16",
"p18": "respuesta18"
}
-->