diff --git a/back/app/Http/Controllers/Administracion/PreguntaController.php b/back/app/Http/Controllers/Administracion/PreguntaController.php
index fb93216..226950c 100644
--- a/back/app/Http/Controllers/Administracion/PreguntaController.php
+++ b/back/app/Http/Controllers/Administracion/PreguntaController.php
@@ -57,209 +57,185 @@ class PreguntaController extends Controller
}
}
- public function getPregunta($id)
- {
- $pregunta = Pregunta::find($id);
- if (!$pregunta) {
- return response()->json([
- 'success' => false,
- 'message' => 'Pregunta no encontrada'
- ], 404);
- }
+public function getPregunta($id)
+{
+ $pregunta = Pregunta::find($id);
+
+ if (!$pregunta) {
return response()->json([
- 'success' => true,
- 'data' => $pregunta
- ]);
+ 'success' => false,
+ 'message' => 'Pregunta no encontrada'
+ ], 404);
}
- public function agregarPreguntaCurso(Request $request)
- {
- try {
- $user = auth()->user();
+$pregunta->imagenes = collect($pregunta->imagenes ?? [])->map(fn($path) => $path ? url(Storage::url($path)) : null);
+$pregunta->imagenes_explicacion = collect($pregunta->imagenes_explicacion ?? [])->map(fn($path) => $path ? url(Storage::url($path)) : null);
- if (!$user->hasRole('Admin')) {
- return response()->json([
- 'success' => false,
- 'message' => 'No autorizado'
- ], 403);
- }
+ return response()->json([
+ 'success' => true,
+ 'data' => $pregunta
+ ]);
+}
- $validator = Validator::make($request->all(), [
- 'curso_id' => 'required|exists:cursos,id',
- 'enunciado' => 'required|string',
- 'enunciado_adicional' => 'nullable|string',
- 'opciones' => 'required|array|min:2',
- 'opciones.*' => 'required|string',
- 'respuesta_correcta' => 'required|string',
- 'explicacion' => 'nullable|string',
- 'nivel_dificultad' => 'required|in:facil,medio,dificil',
- 'activo' => 'boolean',
- 'imagenes' => 'nullable|array',
- 'imagenes.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
- 'imagenes_explicacion' => 'nullable|array',
- 'imagenes_explicacion.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
- ], [
- 'opciones.required' => 'Debe agregar al menos 2 opciones',
- 'opciones.min' => 'Debe agregar al menos 2 opciones',
- 'respuesta_correcta.required' => 'Debe seleccionar una respuesta correcta',
- ]);
+public function agregarPreguntaCurso(Request $request)
+{
+ try {
+ $user = auth()->user();
+ if (!$user->hasRole('administrador')) {
+ return response()->json(['success' => false, 'message' => 'No autorizado'], 403);
+ }
- if ($validator->fails()) {
- return response()->json([
- 'success' => false,
- 'errors' => $validator->errors()
- ], 422);
- }
+ // Validación (igual que antes)
+ $validator = Validator::make($request->all(), [
+ 'curso_id' => 'required|exists:cursos,id',
+ 'enunciado' => 'required|string',
+ 'enunciado_adicional' => 'nullable|string',
+ 'opciones' => 'required',
+ 'respuesta_correcta' => 'required|string',
+ 'explicacion' => 'nullable|string',
+ 'nivel_dificultad' => 'required|in:facil,medio,dificil',
+ 'activo' => 'boolean',
+ 'imagenes' => 'nullable|array',
+ 'imagenes.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
+ 'imagenes_explicacion' => 'nullable|array',
+ 'imagenes_explicacion.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
+ ]);
- // Validar que la respuesta correcta esté en las opciones
- if (!in_array($request->respuesta_correcta, $request->opciones)) {
- return response()->json([
- 'success' => false,
- 'errors' => ['respuesta_correcta' => ['La respuesta correcta debe estar entre las opciones']]
- ], 422);
- }
+ if ($validator->fails()) {
+ return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
+ }
+
+ // Opciones
+ $opciones = is_string($request->opciones) ? json_decode($request->opciones, true) : $request->opciones;
+ $opcionesValidas = array_map('trim', $opciones);
- // Procesar imágenes del enunciado
- $imagenesPaths = [];
- if ($request->hasFile('imagenes')) {
- foreach ($request->file('imagenes') as $imagen) {
- $path = $imagen->store('preguntas/enunciados', 'public');
- $imagenesPaths[] = $path;
- }
+ // Validar respuesta correcta
+ if (!in_array($request->respuesta_correcta, $opcionesValidas)) {
+ return response()->json(['success' => false, 'errors' => ['respuesta_correcta' => ['La respuesta correcta debe estar entre las opciones']]] ,422);
+ }
+
+ // Procesar imágenes del enunciado y devolver URLs completas
+ $imagenesUrls = [];
+ if ($request->hasFile('imagenes')) {
+ foreach ($request->file('imagenes') as $imagen) {
+ $path = $imagen->store('preguntas/enunciados', 'public');
+ $imagenesUrls[] = url(Storage::url($path)); // URL completa
}
+ }
- // Procesar imágenes de la explicación
- $imagenesExplicacionPaths = [];
- if ($request->hasFile('imagenes_explicacion')) {
- foreach ($request->file('imagenes_explicacion') as $imagen) {
- $path = $imagen->store('preguntas/explicaciones', 'public');
- $imagenesExplicacionPaths[] = $path;
- }
+ // Procesar imágenes de la explicación
+ $imagenesExplicacionUrls = [];
+ if ($request->hasFile('imagenes_explicacion')) {
+ foreach ($request->file('imagenes_explicacion') as $imagen) {
+ $path = $imagen->store('preguntas/explicaciones', 'public');
+ $imagenesExplicacionUrls[] = url(Storage::url($path)); // URL completa
}
+ }
- $pregunta = Pregunta::create([
- 'curso_id' => $request->curso_id,
- 'enunciado' => $request->enunciado,
- 'enunciado_adicional' => $request->enunciado_adicional,
- 'opciones' => $request->opciones,
- 'respuesta_correcta' => $request->respuesta_correcta,
- 'explicacion' => $request->explicacion,
- 'nivel_dificultad' => $request->nivel_dificultad,
- 'activo' => $request->boolean('activo'),
- 'imagenes' => $imagenesPaths,
- 'imagenes_explicacion' => $imagenesExplicacionPaths,
- ]);
+ // Crear pregunta
+ $pregunta = Pregunta::create([
+ 'curso_id' => $request->curso_id,
+ 'enunciado' => $request->enunciado,
+ 'enunciado_adicional' => $request->enunciado_adicional,
+ 'opciones' => $opcionesValidas,
+ 'respuesta_correcta' => $request->respuesta_correcta,
+ 'explicacion' => $request->explicacion,
+ 'nivel_dificultad' => $request->nivel_dificultad,
+ 'activo' => $request->boolean('activo'),
+ 'imagenes' => $imagenesUrls,
+ 'imagenes_explicacion' => $imagenesExplicacionUrls,
+ ]);
- Log::info('Pregunta creada', [
- 'pregunta_id' => $pregunta->id,
- 'curso_id' => $request->curso_id,
- 'user_id' => $user->id
- ]);
+ return response()->json(['success' => true, 'message' => 'Pregunta creada correctamente', 'data' => $pregunta], 201);
- return response()->json([
- 'success' => true,
- 'message' => 'Pregunta creada correctamente',
- 'data' => $pregunta
- ], 201);
+ } catch (\Exception $e) {
+ Log::error('Error creando pregunta', ['error' => $e->getMessage()]);
+ return response()->json(['success' => false, 'message' => 'Error al crear la pregunta'], 500);
+ }
+}
- } catch (\Exception $e) {
- Log::error('Error creando pregunta', ['error' => $e->getMessage()]);
- return response()->json([
- 'success' => false,
- 'message' => 'Error al crear la pregunta'
- ], 500);
+public function actualizarPregunta(Request $request, $id)
+{
+ try {
+ $user = auth()->user();
+ if (!$user->hasRole('administrador')) {
+ return response()->json(['success' => false, 'message' => 'No autorizado'], 403);
}
- }
- public function actualizarPregunta(Request $request, $id)
- {
$pregunta = Pregunta::find($id);
-
if (!$pregunta) {
- return response()->json([
- 'success' => false,
- 'message' => 'Pregunta no encontrada'
- ], 404);
+ return response()->json(['success' => false, 'message' => 'Pregunta no encontrada'], 404);
}
+ // Validación (igual que antes)
$validator = Validator::make($request->all(), [
- 'enunciado' => 'required|string',
+ 'curso_id' => 'required|exists:cursos,id',
+ 'enunciado' => 'required|string',
'enunciado_adicional' => 'nullable|string',
- 'opciones' => 'required|array|min:2',
- 'opciones.*' => 'required|string',
- 'respuesta_correcta' => 'required|string',
- 'explicacion' => 'nullable|string',
- 'nivel_dificultad' => 'required|in:facil,medio,dificil',
- 'activo' => 'boolean',
- 'imagenes' => 'nullable|array',
- 'imagenes.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
- 'imagenes_explicacion' => 'nullable|array',
+ 'opciones' => 'required',
+ 'respuesta_correcta' => 'required|string',
+ 'explicacion' => 'nullable|string',
+ 'nivel_dificultad' => 'required|in:facil,medio,dificil',
+ 'activo' => 'boolean',
+ 'imagenes' => 'nullable|array',
+ 'imagenes.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
+ 'imagenes_explicacion' => 'nullable|array',
'imagenes_explicacion.*' => 'image|mimes:jpg,jpeg,png,gif,webp|max:2048',
]);
if ($validator->fails()) {
- return response()->json([
- 'success' => false,
- 'errors' => $validator->errors()
- ], 422);
+ return response()->json(['success' => false, 'errors' => $validator->errors()], 422);
}
- // Validar que la respuesta correcta esté en las opciones
- if (!in_array($request->respuesta_correcta, $request->opciones)) {
- return response()->json([
- 'success' => false,
- 'errors' => ['respuesta_correcta' => ['La respuesta correcta debe estar entre las opciones']]
- ], 422);
+ // Opciones
+ $opciones = is_string($request->opciones) ? json_decode($request->opciones, true) : $request->opciones;
+ $opcionesValidas = array_map('trim', $opciones);
+
+ if (!in_array($request->respuesta_correcta, $opcionesValidas)) {
+ return response()->json(['success' => false, 'errors' => ['respuesta_correcta' => ['La respuesta correcta debe estar entre las opciones']]], 422);
}
- // Procesar nuevas imágenes del enunciado
- $imagenesActuales = $pregunta->imagenes ?? [];
+ // Imágenes del enunciado
+ $imagenesActuales = $request->imagenes_existentes ?? $pregunta->imagenes ?? [];
if ($request->hasFile('imagenes')) {
foreach ($request->file('imagenes') as $imagen) {
$path = $imagen->store('preguntas/enunciados', 'public');
- $imagenesActuales[] = $path;
+ $imagenesActuales[] = url(Storage::url($path));
}
}
- // Procesar nuevas imágenes de la explicación
- $imagenesExplicacionActuales = $pregunta->imagenes_explicacion ?? [];
+ // Imágenes de la explicación
+ $imagenesExplicacionActuales = $request->imagenes_explicacion_existentes ?? $pregunta->imagenes_explicacion ?? [];
if ($request->hasFile('imagenes_explicacion')) {
foreach ($request->file('imagenes_explicacion') as $imagen) {
$path = $imagen->store('preguntas/explicaciones', 'public');
- $imagenesExplicacionActuales[] = $path;
+ $imagenesExplicacionActuales[] = url(Storage::url($path));
}
}
- // Si se enviaron imágenes existentes en edición
- if ($request->has('imagenes_existentes')) {
- $imagenesActuales = $request->imagenes_existentes;
- }
-
- if ($request->has('imagenes_explicacion_existentes')) {
- $imagenesExplicacionActuales = $request->imagenes_explicacion_existentes;
- }
-
$pregunta->update([
- 'enunciado' => $request->enunciado,
+ 'curso_id' => $request->curso_id,
+ 'enunciado' => $request->enunciado,
'enunciado_adicional' => $request->enunciado_adicional,
- 'opciones' => $request->opciones,
- 'respuesta_correcta' => $request->respuesta_correcta,
- 'explicacion' => $request->explicacion,
- 'nivel_dificultad' => $request->nivel_dificultad,
- 'activo' => $request->boolean('activo'),
- 'imagenes' => $imagenesActuales,
+ 'opciones' => $opcionesValidas,
+ 'respuesta_correcta' => $request->respuesta_correcta,
+ 'explicacion' => $request->explicacion,
+ 'nivel_dificultad' => $request->nivel_dificultad,
+ 'activo' => $request->boolean('activo'),
+ 'imagenes' => $imagenesActuales,
'imagenes_explicacion' => $imagenesExplicacionActuales,
]);
- return response()->json([
- 'success' => true,
- 'message' => 'Pregunta actualizada correctamente',
- 'data' => $pregunta
- ]);
+ return response()->json(['success' => true, 'message' => 'Pregunta actualizada correctamente', 'data' => $pregunta]);
+
+ } catch (\Exception $e) {
+ Log::error('Error actualizando pregunta', ['error' => $e->getMessage()]);
+ return response()->json(['success' => false, 'message' => 'Error al actualizar la pregunta'], 500);
}
+}
public function eliminarPregunta($id)
{
diff --git a/back/database/seeders/RolePermissionSeeder.php b/back/database/seeders/RolePermissionSeeder.php
new file mode 100644
index 0000000..e69de29
diff --git a/back/database/seeders/RoleSeeder.php b/back/database/seeders/RoleSeeder.php
index b641064..3b92244 100644
--- a/back/database/seeders/RoleSeeder.php
+++ b/back/database/seeders/RoleSeeder.php
@@ -5,27 +5,54 @@ namespace Database\Seeders;
use Illuminate\Database\Seeder;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
-use Illuminate\Support\Facades\Schema;
+use Spatie\Permission\PermissionRegistrar;
class RoleSeeder extends Seeder
{
public function run(): void
{
- // Evita problemas si se vuelve a ejecutar
- app()[\Spatie\Permission\PermissionRegistrar::class]->forgetCachedPermissions();
+ // 🔥 Limpiar cache de permisos para evitar conflictos
+ app()[PermissionRegistrar::class]->forgetCachedPermissions();
- // Roles base
+ /* ================= PERMISOS ================= */
+ $permissions = [
+ // Ejemplo: CRUD de preguntas
+ 'ver-preguntas',
+ 'crear-preguntas',
+ 'editar-preguntas',
+ 'eliminar-preguntas',
+
+ // Ejemplo: CRUD de cursos
+ 'ver-cursos',
+ 'crear-cursos',
+ 'editar-cursos',
+ 'eliminar-cursos',
+ ];
+
+ foreach ($permissions as $permission) {
+ Permission::firstOrCreate([
+ 'name' => $permission,
+ 'guard_name' => 'web',
+ ]);
+ }
+
+ /* ================= ROLES ================= */
$roles = [
- 'usuario',
- 'administrador',
- 'superadmin',
+ 'usuario' => [], // rol básico sin permisos
+ 'administrador' => $permissions, // asigna todos los permisos al administrador
+ 'superadmin' => $permissions, // opcionalmente igual que administrador
];
- foreach ($roles as $role) {
- Role::firstOrCreate([
- 'name' => $role,
+ foreach ($roles as $roleName => $rolePermissions) {
+ $role = Role::firstOrCreate([
+ 'name' => $roleName,
'guard_name' => 'web',
]);
+
+ // Asignar permisos si los hay
+ if (!empty($rolePermissions)) {
+ $role->syncPermissions($rolePermissions);
+ }
}
}
}
diff --git a/front/package-lock.json b/front/package-lock.json
index 166a51f..d10d92b 100644
--- a/front/package-lock.json
+++ b/front/package-lock.json
@@ -1110,6 +1110,7 @@
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -1119,6 +1120,7 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -1224,6 +1226,7 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -1233,7 +1236,6 @@
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
"integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@kurkle/color": "^0.3.0"
},
@@ -1246,6 +1248,7 @@
"resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
"integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
"license": "ISC",
+ "peer": true,
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
@@ -1257,6 +1260,7 @@
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -1268,7 +1272,8 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -1331,6 +1336,7 @@
"resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
"integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1348,7 +1354,8 @@
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
"integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/dom-align": {
"version": "1.12.4",
@@ -1380,7 +1387,8 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
- "license": "MIT"
+ "license": "MIT",
+ "peer": true
},
"node_modules/entities": {
"version": "7.0.1",
@@ -1510,6 +1518,7 @@
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@@ -1583,6 +1592,7 @@
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
+ "peer": true,
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
@@ -1686,6 +1696,7 @@
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -1722,6 +1733,7 @@
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-locate": "^4.1.0"
},
@@ -1827,6 +1839,7 @@
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-try": "^2.0.0"
},
@@ -1842,6 +1855,7 @@
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"p-limit": "^2.2.0"
},
@@ -1854,6 +1868,7 @@
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=6"
}
@@ -1863,6 +1878,7 @@
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=8"
}
@@ -1885,7 +1901,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -1919,6 +1934,7 @@
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
"integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=10.13.0"
}
@@ -1980,6 +1996,7 @@
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1988,7 +2005,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/resize-observer-polyfill": {
"version": "1.5.1",
@@ -2060,7 +2078,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/shallow-equal": {
"version": "1.2.1",
@@ -2091,6 +2110,7 @@
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
@@ -2105,6 +2125,7 @@
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -2168,7 +2189,6 @@
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -2243,7 +2263,6 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz",
"integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.27",
"@vue/compiler-sfc": "3.5.27",
@@ -2335,13 +2354,15 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
"integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
@@ -2355,13 +2376,15 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
"integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
- "license": "ISC"
+ "license": "ISC",
+ "peer": true
},
"node_modules/yargs": {
"version": "15.4.1",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
"integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"cliui": "^6.0.0",
"decamelize": "^1.2.0",
@@ -2384,6 +2407,7 @@
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
"integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
"license": "ISC",
+ "peer": true,
"dependencies": {
"camelcase": "^5.0.0",
"decamelize": "^1.2.0"
diff --git a/front/src/router/index.js b/front/src/router/index.js
index 4f7b4a3..c772e72 100644
--- a/front/src/router/index.js
+++ b/front/src/router/index.js
@@ -14,6 +14,11 @@ const routes = [
component: Login,
meta: { guest: true }
},
+ {
+ path: '/unauthorized',
+ name: 'Unauthorized',
+ component: () => import('../views/403.vue')
+ },
{
path: '/usuario/dashboard',
name: 'dashboard',
@@ -51,14 +56,14 @@ const routes = [
path: '/admin/dashboard/cursos/:id/preguntas',
name: 'CursoPreguntas',
component: () => import('../views/administrador/cursos/PreguntasCursoView.vue'),
- meta: { requiresAuth: true }
+ meta: { requiresAuth: true, role: 'administrador' }
},
{
path: '/admin/dashboard/procesos',
name: 'Procesos',
component: () => import('../views/administrador/Procesos/ProcesosList.vue'),
- meta: { requiresAuth: true }
+ meta: { requiresAuth: true, role: 'administrador' }
}
]
diff --git a/front/src/store/pregunta.store.js b/front/src/store/pregunta.store.js
index 0f7acce..e5d0121 100644
--- a/front/src/store/pregunta.store.js
+++ b/front/src/store/pregunta.store.js
@@ -56,61 +56,57 @@ export const usePreguntaStore = defineStore('pregunta', {
/* ===============================
CREAR PREGUNTA (CON IMÁGENES)
=============================== */
- async crearPregunta(data) {
+async crearPregunta(formData) {
this.loading = true
this.errors = null
-
+
try {
- const formData = new FormData()
-
- // Campos simples
- formData.append('curso_id', data.curso_id)
- formData.append('enunciado', data.enunciado)
- formData.append('nivel_dificultad', data.nivel_dificultad)
-
- if (data.enunciado_adicional)
- formData.append('enunciado_adicional', data.enunciado_adicional)
-
- if (data.respuesta_correcta)
- formData.append('respuesta_correcta', data.respuesta_correcta)
-
- if (data.explicacion)
- formData.append('explicacion', data.explicacion)
-
- if (data.opciones)
- formData.append('opciones', JSON.stringify(data.opciones))
-
- // Imágenes del enunciado
- if (data.imagenes?.length) {
- data.imagenes.forEach(img => {
- formData.append('imagenes[]', img)
- })
+ const response = await api.post('/admin/preguntas', formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
+ })
+
+ if (response.data.success) {
+ return response.data
+ } else {
+ throw new Error(response.data.message || 'Error al crear pregunta')
}
-
- // Imágenes de la explicación
- if (data.imagenes_explicacion?.length) {
- data.imagenes_explicacion.forEach(img => {
- formData.append('imagenes_explicacion[]', img)
- })
+ } catch (error) {
+ if (error.response?.status === 422) {
+ this.errors = error.response.data.errors
}
-
- const res = await api.post('/admin/preguntas', formData, {
- headers: { 'Content-Type': 'multipart/form-data' },
+ throw error
+ } finally {
+ this.loading = false
+ }
+ },
+
+ async actualizarPregunta(id, formData) {
+ this.loading = true
+ this.errors = null
+
+ try {
+ const response = await api.post(`/admin/preguntas/${id}`, formData, {
+ headers: {
+ 'Content-Type': 'multipart/form-data'
+ }
})
-
- this.preguntas.unshift(res.data.data)
- return res.data.data
+
+ if (response.data.success) {
+ return response.data
+ } else {
+ throw new Error(response.data.message || 'Error al actualizar pregunta')
+ }
} catch (error) {
- this.errors =
- error.response?.status === 422
- ? error.response.data.errors
- : error.response?.data || error.message
+ if (error.response?.status === 422) {
+ this.errors = error.response.data.errors
+ }
throw error
} finally {
this.loading = false
}
},
-
/* ===============================
ACTUALIZAR PREGUNTA (SUMA IMÁGENES)
=============================== */
diff --git a/front/src/views/Login.vue b/front/src/views/Login.vue
index c4b3e13..6b05dfc 100644
--- a/front/src/views/Login.vue
+++ b/front/src/views/Login.vue
@@ -25,64 +25,61 @@
class="login-form"
>
-