diff --git a/.claude/settings.local.json b/.claude/settings.local.json index cfa11400..181dfe6d 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -18,7 +18,9 @@ "Bash(php -l app/Http/Controllers/GrupoController.php)", "Bash(php -l app/Models/Tarea.php)", "Bash(php -l app/Models/Entrega.php)", - "Bash(php -l app/Livewire/GrupoDocente.php)" + "Bash(php -l app/Livewire/GrupoDocente.php)", + "Bash(composer require *)", + "Bash(Select-String -Pattern \"entrega|Entrega\" -Context 0,0)" ] } } diff --git a/app/Http/Controllers/AlumnoController.php b/app/Http/Controllers/AlumnoController.php index a3817dd1..10fc230a 100644 --- a/app/Http/Controllers/AlumnoController.php +++ b/app/Http/Controllers/AlumnoController.php @@ -51,9 +51,7 @@ class AlumnoController extends Controller }) ->get(); - } - - if ($user->hasRole('Control escolar')) { + }else{ $plantelesUsuario = $user->plantelUsuarios->pluck('id'); $alumnos = Alumno::with(['alumnos.plantelUsuarios','statusAlumno']) @@ -65,6 +63,8 @@ class AlumnoController extends Controller } + + return view('alumno.index', compact('alumnos')); } diff --git a/app/Http/Controllers/EntregaController.php b/app/Http/Controllers/EntregaController.php index 167e3b26..e33c1116 100644 --- a/app/Http/Controllers/EntregaController.php +++ b/app/Http/Controllers/EntregaController.php @@ -9,6 +9,7 @@ use App\Models\Tarea; use Illuminate\Http\Request; use Illuminate\Routing\Controller; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\DB; class EntregaController extends Controller { @@ -25,12 +26,23 @@ class EntregaController extends Controller ]); } - $cicloActual = Campan::where('status', 1)->first(); + // Ciclo más alto del alumno = semestre en curso + $maxCiclo = DB::table('alumno_grupo') + ->join('grupos', 'grupos.id', '=', 'alumno_grupo.grupo_id') + ->where('alumno_grupo.alumno_id', $alumno->id) + ->max('grupos.ciclo'); - // Solo grupos del ciclo activo - $gruposActuales = $alumno->grupos() - ->when($cicloActual, fn($q) => $q->where('ciclo', $cicloActual->id)) - ->pluck('grupos.id'); + // Campan para mostrar el nombre en la vista (busca por id = ciclo, o cae al activo) + $cicloActual = $maxCiclo ? Campan::find($maxCiclo) ?? Campan::where('status', 1)->first() : null; + + // Grupos del alumno en ese ciclo + $gruposActuales = $maxCiclo + ? DB::table('alumno_grupo') + ->join('grupos', 'grupos.id', '=', 'alumno_grupo.grupo_id') + ->where('alumno_grupo.alumno_id', $alumno->id) + ->where('grupos.ciclo', $maxCiclo) + ->pluck('alumno_grupo.grupo_id') + : collect(); $todasTareas = Tarea::whereIn('grupo_id', $gruposActuales) ->with(['material', 'grupo']) @@ -59,15 +71,21 @@ class EntregaController extends Controller public function store(Request $request, Tarea $tarea) { $request->validate([ - 'archivo' => 'nullable|file|max:20480', + 'archivo' => 'nullable|file|max:2048', 'texto' => 'nullable|string|max:5000', ]); if (!$request->hasFile('archivo') && !filled($request->texto)) { - return back()->withErrors(['entrega' => 'Debes escribir un texto o adjuntar un archivo.']); + return redirect()->route('mis.tareas') + ->withErrors(['entrega' => 'Debes escribir un texto o adjuntar un archivo.']); } - $alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->firstOrFail(); + $alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first(); + + if (!$alumno) { + return redirect()->route('mis.tareas') + ->with('error', 'No se encontró un perfil de alumno para tu cuenta.'); + } $entrega = Entrega::where('tarea_id', $tarea->id) ->where('alumno_id', $alumno->id) @@ -82,12 +100,12 @@ class EntregaController extends Controller } if ($entrega->status !== 'sin_subir') { - return back()->with('error', 'Ya entregaste esta tarea y no puedes volver a subirla.'); + return redirect()->route('mis.tareas') + ->with('error', 'Ya entregaste esta tarea y no puedes volver a subirla.'); } - $path = null; + $path = $entrega->archivo; if ($request->hasFile('archivo')) { - // Almacena en disco local (privado) igual que el FileController espera $path = $request->file('archivo')->store("entregas/{$tarea->id}"); } @@ -97,6 +115,7 @@ class EntregaController extends Controller 'status' => 'pendiente', ]); - return back()->with('info', 'Tarea enviada correctamente. Queda pendiente de calificación.'); + return redirect()->route('mis.tareas') + ->with('info', 'Tarea enviada correctamente. Queda pendiente de calificación.'); } } diff --git a/app/Http/Controllers/EvaluacionReporteController.php b/app/Http/Controllers/EvaluacionReporteController.php new file mode 100644 index 00000000..f14960f1 --- /dev/null +++ b/app/Http/Controllers/EvaluacionReporteController.php @@ -0,0 +1,222 @@ +buildDatos($evaluacion); + $totalSubmissions = $evaluacion->submissions()->count(); + + return view('evaluacion.reporte', compact('evaluacion', 'datos', 'totalSubmissions')); + } + + public function pdf(Evaluacion $evaluacion) + { + $datos = $this->buildDatos($evaluacion); + $totalSubmissions = $evaluacion->submissions()->count(); + + // Fetch chart images from QuickChart and embed as base64 + foreach ($datos as &$item) { + if ($item['tipo'] === 'texto_libre') continue; + + if ($item['es_docente']) { + foreach ($item['por_docente'] as &$pd) { + if (isset($pd['chart_config'])) { + $pd['chart_img'] = $this->fetchChartBase64($pd['chart_config']); + } + } + } else { + if (isset($item['chart_config'])) { + $item['chart_img'] = $this->fetchChartBase64($item['chart_config']); + } + } + } + + $pdf = Pdf::loadView('evaluacion.reporte_pdf', compact('evaluacion', 'datos', 'totalSubmissions')) + ->setPaper('a4', 'portrait'); + + return $pdf->download('reporte-' . $evaluacion->id . '.pdf'); + } + + // ─── Data builder ─────────────────────────────────────────────────────────── + + private function buildDatos(Evaluacion $evaluacion): array + { + $preguntas = $evaluacion->preguntas() + ->with(['tipo', 'opciones', 'respuestas.opcion']) + ->get(); + + $datos = []; + + foreach ($preguntas as $pregunta) { + $tipoSlug = $pregunta->tipo->slug; + $respuestas = $pregunta->respuestas; + + if ($pregunta->es_docente) { + $docenteIds = $respuestas->pluck('docente_id')->unique()->filter(); + $porDocente = []; + + foreach ($docenteIds as $docenteId) { + $rsp = $respuestas->where('docente_id', $docenteId); + $docente = Docente::with('docentes')->find($docenteId); + $user = $docente?->docentes->first(); + $nombre = $user + ? trim("{$user->apellidoPaterno} {$user->apellidoMaterno} {$user->name}") + : "Docente #{$docenteId}"; + + $chart = $this->procesarRespuestas($rsp, $tipoSlug, $pregunta); + + $porDocente[] = [ + 'docente_nombre' => $nombre, + 'chart_config' => $chart['config'] ?? null, + 'textos' => $chart['textos'] ?? [], + 'promedio' => $chart['promedio'] ?? null, + 'total' => $rsp->count(), + ]; + } + + $datos[] = [ + 'pregunta' => $pregunta, + 'tipo' => $tipoSlug, + 'es_docente' => true, + 'por_docente' => $porDocente, + ]; + } else { + $chart = $this->procesarRespuestas($respuestas, $tipoSlug, $pregunta); + + $datos[] = [ + 'pregunta' => $pregunta, + 'tipo' => $tipoSlug, + 'es_docente' => false, + 'chart_config' => $chart['config'] ?? null, + 'textos' => $chart['textos'] ?? [], + 'promedio' => $chart['promedio'] ?? null, + 'total' => $respuestas->count(), + ]; + } + } + + return $datos; + } + + private function procesarRespuestas($respuestas, string $tipoSlug, $pregunta): array + { + switch ($tipoSlug) { + + case 'si_no': + $si = $respuestas->where('valor', 'si')->count(); + $no = $respuestas->where('valor', 'no')->count(); + return ['config' => [ + 'type' => 'doughnut', + 'data' => [ + 'labels' => ['Sí', 'No'], + 'datasets' => [[ + 'data' => [$si, $no], + 'backgroundColor' => ['#1cc88a', '#e74a3b'], + 'borderWidth' => 2, + ]], + ], + 'options' => [ + 'plugins' => [ + 'legend' => ['position' => 'bottom'], + 'datalabels' => ['display' => false], + ], + ], + ]]; + + case 'opcion_multiple': + $labels = []; + $counts = []; + $colors = ['#4e73df','#1cc88a','#36b9cc','#f6c23e','#e74a3b','#858796','#5a5c69']; + foreach ($pregunta->opciones as $idx => $opcion) { + $labels[] = $opcion->texto; + $counts[] = $respuestas->where('opcion_id', $opcion->id)->count(); + } + return ['config' => [ + 'type' => 'bar', + 'data' => [ + 'labels' => $labels, + 'datasets' => [[ + 'label' => 'Respuestas', + 'data' => $counts, + 'backgroundColor' => array_slice(array_merge($colors, $colors), 0, count($labels)), + 'borderRadius' => 4, + ]], + ], + 'options' => [ + 'plugins' => ['legend' => ['display' => false]], + 'scales' => ['y' => ['beginAtZero' => true, 'ticks' => ['stepSize' => 1]]], + ], + ]]; + + case 'escala_5': + case 'escala_10': + $max = $tipoSlug === 'escala_5' ? 5 : 10; + $labels = range(1, $max); + $counts = []; + foreach ($labels as $val) { + $counts[] = $respuestas->filter(fn($r) => (int)$r->valor === $val)->count(); + } + $total = $respuestas->count(); + $suma = $respuestas->sum(fn($r) => (int)$r->valor); + $prom = $total > 0 ? round($suma / $total, 2) : null; + + return [ + 'config' => [ + 'type' => 'bar', + 'data' => [ + 'labels' => $labels, + 'datasets' => [[ + 'label' => 'Respuestas', + 'data' => $counts, + 'backgroundColor' => '#36b9cc', + 'borderRadius' => 4, + ]], + ], + 'options' => [ + 'plugins' => ['legend' => ['display' => false]], + 'scales' => ['y' => ['beginAtZero' => true, 'ticks' => ['stepSize' => 1]]], + ], + ], + 'promedio' => $prom, + ]; + + case 'texto_libre': + return ['textos' => $respuestas->pluck('valor')->filter()->values()->toArray()]; + + default: + return []; + } + } + + // ─── QuickChart helper ─────────────────────────────────────────────────────── + + private function fetchChartBase64(array $config): ?string + { + try { + $response = Http::timeout(15)->post('https://quickchart.io/chart', [ + 'chart' => $config, + 'width' => 500, + 'height' => 280, + 'backgroundColor' => 'white', + 'format' => 'png', + ]); + + if ($response->successful()) { + return 'data:image/png;base64,' . base64_encode($response->body()); + } + } catch (\Throwable) { + // QuickChart no disponible; la imagen simplemente no aparece + } + + return null; + } +} diff --git a/app/Http/Controllers/EvaluacionResponderController.php b/app/Http/Controllers/EvaluacionResponderController.php index 62af8625..1e9e4629 100644 --- a/app/Http/Controllers/EvaluacionResponderController.php +++ b/app/Http/Controllers/EvaluacionResponderController.php @@ -98,12 +98,35 @@ class EvaluacionResponderController extends Controller if ($tipoSlug === 'alumno' && $preguntas->where('es_docente', true)->count() > 0) { $alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first(); if ($alumno) { - $gruposIds = $alumno->grupos()->pluck('grupos.id'); - $docentesParaEvaluar = Docente::whereHas('grupos', fn($q) => $q->whereIn('grupos.id', $gruposIds)) - ->with('docentes') - ->get() - ->unique('id') - ->values(); + // Ciclo más alto del alumno = semestre actual + $cicloActual = DB::table('alumno_grupo') + ->join('grupos', 'grupos.id', '=', 'alumno_grupo.grupo_id') + ->where('alumno_grupo.alumno_id', $alumno->id) + ->max('grupos.ciclo'); + + if ($cicloActual) { + // Grupos del alumno en ese ciclo + $gruposIds = DB::table('alumno_grupo') + ->join('grupos', 'grupos.id', '=', 'alumno_grupo.grupo_id') + ->where('alumno_grupo.alumno_id', $alumno->id) + ->where('grupos.ciclo', $cicloActual) + ->pluck('alumno_grupo.grupo_id') + ->toArray(); + + // Docentes asignados a esos grupos cuya materia tenga el mismo ciclo que el grupo + $docenteIds = DB::table('docente_grupo') + ->join('materials', 'materials.id', '=', 'docente_grupo.materia_id') + ->whereIn('docente_grupo.grupo_id', $gruposIds) + ->where('materials.ciclo', $cicloActual) + ->distinct() + ->pluck('docente_grupo.docente_id') + ->toArray(); + + $docentesParaEvaluar = Docente::whereIn('id', $docenteIds) + ->with('docentes') + ->get() + ->values(); + } } } diff --git a/app/Http/Controllers/GrupoController.php b/app/Http/Controllers/GrupoController.php index a190359f..eca77703 100644 --- a/app/Http/Controllers/GrupoController.php +++ b/app/Http/Controllers/GrupoController.php @@ -27,7 +27,7 @@ class GrupoController extends Controller { $user = Auth::user(); - if ($user->hasRole('Admin')) { + if ($user->hasRole('Administrador')) { $grupos = Grupo::all(); } diff --git a/app/Http/Controllers/ProspectoController.php b/app/Http/Controllers/ProspectoController.php new file mode 100644 index 00000000..fd1beed1 --- /dev/null +++ b/app/Http/Controllers/ProspectoController.php @@ -0,0 +1,10 @@ +get(); } + private function refreshMateriasSelect(): void + { + $grupo = $this->grupo; + $materiasAsignadas = DB::table('docente_grupo') + ->where('grupo_id', $grupo->id) + ->pluck('materia_id') + ->toArray(); + + $this->materiasSelect = Material::whereHas('carreraMaterial', function ($query) use ($grupo) { + $query->where('carreras.id', $grupo->plan->carrera); + }) + ->where('ciclo', $grupo->ciclo) + ->when(!empty($materiasAsignadas), fn($q) => $q->whereNotIn('id', $materiasAsignadas)) + ->get(); + } + public function guardar() { $this->validate([ @@ -71,6 +87,7 @@ class GrupoDocente extends Component ]); $this->reset(['docente_id', 'materia_id']); + $this->refreshMateriasSelect(); $this->dispatch('close-modal-docente'); $this->dispatch('swal', icon: 'success', title: '¡Listo!', @@ -96,6 +113,8 @@ class GrupoDocente extends Component $this->grupo->docentes()->wherePivot('materia_id', $materiaId)->detach($docenteId); + $this->refreshMateriasSelect(); + $this->dispatch('swal', icon: 'success', title: 'Eliminado', text: 'Docente y sus tareas removidos correctamente', diff --git a/app/Models/Anuncio.php b/app/Models/Anuncio.php index c3fff5e0..7cbdd5e5 100644 --- a/app/Models/Anuncio.php +++ b/app/Models/Anuncio.php @@ -31,7 +31,7 @@ class Anuncio extends Model 'all' => 'Todos', 'Alumno' => 'Alumnos', 'Docente' => 'Docentes', - 'Admin' => 'Administradores', + 'Administrador' => 'Administradores', 'Control escolar' => 'Control escolar', 'Coordinación académica' => 'Coordinación académica', 'Programador' => 'Programadores', diff --git a/composer.json b/composer.json index 51a92533..81033f35 100644 --- a/composer.json +++ b/composer.json @@ -10,6 +10,7 @@ "license": "MIT", "require": { "php": "^8.2", + "barryvdh/laravel-dompdf": "^3.1", "laravel/cashier-paddle": "^2.6", "laravel/framework": "^12.0", "laravel/jetstream": "^5.4", diff --git a/composer.lock b/composer.lock index 02975c53..91b988cc 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "28550ccb2083e2f7896811098a56fc72", + "content-hash": "a61dc0d9947c0fec7f5e1704c31eb732", "packages": [ { "name": "bacon/bacon-qr-code", @@ -61,6 +61,83 @@ }, "time": "2025-11-19T17:15:36+00:00" }, + { + "name": "barryvdh/laravel-dompdf", + "version": "v3.1.2", + "source": { + "type": "git", + "url": "https://github.com/barryvdh/laravel-dompdf.git", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/barryvdh/laravel-dompdf/zipball/ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "reference": "ee3b72b19ccdf57d0243116ecb2b90261344dedc", + "shasum": "" + }, + "require": { + "dompdf/dompdf": "^3.0", + "illuminate/support": "^9|^10|^11|^12|^13.0", + "php": "^8.1" + }, + "require-dev": { + "larastan/larastan": "^2.7|^3.0", + "orchestra/testbench": "^7|^8|^9.16|^10|^11.0", + "phpro/grumphp": "^2.5", + "squizlabs/php_codesniffer": "^3.5" + }, + "type": "library", + "extra": { + "laravel": { + "aliases": { + "PDF": "Barryvdh\\DomPDF\\Facade\\Pdf", + "Pdf": "Barryvdh\\DomPDF\\Facade\\Pdf" + }, + "providers": [ + "Barryvdh\\DomPDF\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "psr-4": { + "Barryvdh\\DomPDF\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Barry vd. Heuvel", + "email": "barryvdh@gmail.com" + } + ], + "description": "A DOMPDF Wrapper for Laravel", + "keywords": [ + "dompdf", + "laravel", + "pdf" + ], + "support": { + "issues": "https://github.com/barryvdh/laravel-dompdf/issues", + "source": "https://github.com/barryvdh/laravel-dompdf/tree/v3.1.2" + }, + "funding": [ + { + "url": "https://fruitcake.nl", + "type": "custom" + }, + { + "url": "https://github.com/barryvdh", + "type": "github" + } + ], + "time": "2026-02-21T08:51:10+00:00" + }, { "name": "brick/math", "version": "0.14.1", @@ -612,6 +689,161 @@ ], "time": "2024-02-05T11:56:58+00:00" }, + { + "name": "dompdf/dompdf", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/dompdf/dompdf.git", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/dompdf/zipball/f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "reference": "f11ead23a8a76d0ff9bbc6c7c8fd7e05ca328496", + "shasum": "" + }, + "require": { + "dompdf/php-font-lib": "^1.0.0", + "dompdf/php-svg-lib": "^1.0.0", + "ext-dom": "*", + "ext-mbstring": "*", + "masterminds/html5": "^2.0", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ext-json": "*", + "ext-zip": "*", + "mockery/mockery": "^1.3", + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11", + "squizlabs/php_codesniffer": "^3.5", + "symfony/process": "^4.4 || ^5.4 || ^6.2 || ^7.0" + }, + "suggest": { + "ext-gd": "Needed to process images", + "ext-gmagick": "Improves image processing performance", + "ext-imagick": "Improves image processing performance", + "ext-zlib": "Needed for pdf stream compression" + }, + "type": "library", + "autoload": { + "psr-4": { + "Dompdf\\": "src/" + }, + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1" + ], + "authors": [ + { + "name": "The Dompdf Community", + "homepage": "https://github.com/dompdf/dompdf/blob/master/AUTHORS.md" + } + ], + "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter", + "homepage": "https://github.com/dompdf/dompdf", + "support": { + "issues": "https://github.com/dompdf/dompdf/issues", + "source": "https://github.com/dompdf/dompdf/tree/v3.1.5" + }, + "time": "2026-03-03T13:54:37+00:00" + }, + { + "name": "dompdf/php-font-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-font-lib.git", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-font-lib/zipball/a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "reference": "a6e9a688a2a80016ac080b97be73d3e10c444c9a", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11 || ^12" + }, + "type": "library", + "autoload": { + "psr-4": { + "FontLib\\": "src/FontLib" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-or-later" + ], + "authors": [ + { + "name": "The FontLib Community", + "homepage": "https://github.com/dompdf/php-font-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse, export and make subsets of different types of font files.", + "homepage": "https://github.com/dompdf/php-font-lib", + "support": { + "issues": "https://github.com/dompdf/php-font-lib/issues", + "source": "https://github.com/dompdf/php-font-lib/tree/1.0.2" + }, + "time": "2026-01-20T14:10:26+00:00" + }, + { + "name": "dompdf/php-svg-lib", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/dompdf/php-svg-lib.git", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/dompdf/php-svg-lib/zipball/8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "reference": "8259ffb930817e72b1ff1caef5d226501f3dfeb1", + "shasum": "" + }, + "require": { + "ext-mbstring": "*", + "php": "^7.1 || ^8.0", + "sabberworm/php-css-parser": "^8.4 || ^9.0" + }, + "require-dev": { + "phpunit/phpunit": "^7.5 || ^8 || ^9 || ^10 || ^11" + }, + "type": "library", + "autoload": { + "psr-4": { + "Svg\\": "src/Svg" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-3.0-or-later" + ], + "authors": [ + { + "name": "The SvgLib Community", + "homepage": "https://github.com/dompdf/php-svg-lib/blob/master/AUTHORS.md" + } + ], + "description": "A library to read, parse and export to PDF SVG files.", + "homepage": "https://github.com/dompdf/php-svg-lib", + "support": { + "issues": "https://github.com/dompdf/php-svg-lib/issues", + "source": "https://github.com/dompdf/php-svg-lib/tree/1.0.2" + }, + "time": "2026-01-02T16:01:13+00:00" + }, { "name": "dragonmantank/cron-expression", "version": "v3.6.0", @@ -2728,6 +2960,73 @@ ], "time": "2026-01-23T05:41:38+00:00" }, + { + "name": "masterminds/html5", + "version": "2.10.0", + "source": { + "type": "git", + "url": "https://github.com/Masterminds/html5-php.git", + "reference": "fcf91eb64359852f00d921887b219479b4f21251" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Masterminds/html5-php/zipball/fcf91eb64359852f00d921887b219479b4f21251", + "reference": "fcf91eb64359852f00d921887b219479b4f21251", + "shasum": "" + }, + "require": { + "ext-dom": "*", + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35 || ^5.7.21 || ^6 || ^7 || ^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.7-dev" + } + }, + "autoload": { + "psr-4": { + "Masterminds\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Matt Butcher", + "email": "technosophos@gmail.com" + }, + { + "name": "Matt Farina", + "email": "matt@mattfarina.com" + }, + { + "name": "Asmir Mustafic", + "email": "goetas@gmail.com" + } + ], + "description": "An HTML5 parser and serializer.", + "homepage": "http://masterminds.github.io/html5-php", + "keywords": [ + "HTML5", + "dom", + "html", + "parser", + "querypath", + "serializer", + "xml" + ], + "support": { + "issues": "https://github.com/Masterminds/html5-php/issues", + "source": "https://github.com/Masterminds/html5-php/tree/2.10.0" + }, + "time": "2025-07-25T09:04:22+00:00" + }, { "name": "mobiledetect/mobiledetectlib", "version": "4.8.09", @@ -5121,6 +5420,86 @@ ], "time": "2024-06-11T12:45:25+00:00" }, + { + "name": "sabberworm/php-css-parser", + "version": "v9.3.0", + "source": { + "type": "git", + "url": "https://github.com/MyIntervals/PHP-CSS-Parser.git", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/MyIntervals/PHP-CSS-Parser/zipball/88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "reference": "88dbd0f7f91abbfe4402d0a3071e9ff4d81ed949", + "shasum": "" + }, + "require": { + "ext-iconv": "*", + "php": "^7.2.0 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", + "thecodingmachine/safe": "^1.3 || ^2.5 || ^3.4" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "1.4.0", + "phpstan/extension-installer": "1.4.3", + "phpstan/phpstan": "1.12.32 || 2.1.32", + "phpstan/phpstan-phpunit": "1.4.2 || 2.0.8", + "phpstan/phpstan-strict-rules": "1.6.2 || 2.0.7", + "phpunit/phpunit": "8.5.52", + "rawr/phpunit-data-provider": "3.3.1", + "rector/rector": "1.2.10 || 2.2.8", + "rector/type-perfect": "1.0.0 || 2.1.0", + "squizlabs/php_codesniffer": "4.0.1", + "thecodingmachine/phpstan-safe-rule": "1.2.0 || 1.4.1" + }, + "suggest": { + "ext-mbstring": "for parsing UTF-8 CSS" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "9.4.x-dev" + } + }, + "autoload": { + "files": [ + "src/Rule/Rule.php", + "src/RuleSet/RuleContainer.php" + ], + "psr-4": { + "Sabberworm\\CSS\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Raphael Schweikert" + }, + { + "name": "Oliver Klee", + "email": "github@oliverklee.de" + }, + { + "name": "Jake Hotson", + "email": "jake.github@qzdesign.co.uk" + } + ], + "description": "Parser for CSS Files written in PHP", + "homepage": "https://www.sabberworm.com/blog/2010/6/10/php-css-parser", + "keywords": [ + "css", + "parser", + "stylesheet" + ], + "support": { + "issues": "https://github.com/MyIntervals/PHP-CSS-Parser/issues", + "source": "https://github.com/MyIntervals/PHP-CSS-Parser/tree/v9.3.0" + }, + "time": "2026-03-03T17:31:43+00:00" + }, { "name": "spatie/laravel-permission", "version": "6.24.0", @@ -7964,6 +8343,149 @@ ], "time": "2025-10-27T20:36:44+00:00" }, + { + "name": "thecodingmachine/safe", + "version": "v3.4.0", + "source": { + "type": "git", + "url": "https://github.com/thecodingmachine/safe.git", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/705683a25bacf0d4860c7dea4d7947bfd09eea19", + "reference": "705683a25bacf0d4860c7dea4d7947bfd09eea19", + "shasum": "" + }, + "require": { + "php": "^8.1" + }, + "require-dev": { + "php-parallel-lint/php-parallel-lint": "^1.4", + "phpstan/phpstan": "^2", + "phpunit/phpunit": "^10", + "squizlabs/php_codesniffer": "^3.2" + }, + "type": "library", + "autoload": { + "files": [ + "lib/special_cases.php", + "generated/apache.php", + "generated/apcu.php", + "generated/array.php", + "generated/bzip2.php", + "generated/calendar.php", + "generated/classobj.php", + "generated/com.php", + "generated/cubrid.php", + "generated/curl.php", + "generated/datetime.php", + "generated/dir.php", + "generated/eio.php", + "generated/errorfunc.php", + "generated/exec.php", + "generated/fileinfo.php", + "generated/filesystem.php", + "generated/filter.php", + "generated/fpm.php", + "generated/ftp.php", + "generated/funchand.php", + "generated/gettext.php", + "generated/gmp.php", + "generated/gnupg.php", + "generated/hash.php", + "generated/ibase.php", + "generated/ibmDb2.php", + "generated/iconv.php", + "generated/image.php", + "generated/imap.php", + "generated/info.php", + "generated/inotify.php", + "generated/json.php", + "generated/ldap.php", + "generated/libxml.php", + "generated/lzf.php", + "generated/mailparse.php", + "generated/mbstring.php", + "generated/misc.php", + "generated/mysql.php", + "generated/mysqli.php", + "generated/network.php", + "generated/oci8.php", + "generated/opcache.php", + "generated/openssl.php", + "generated/outcontrol.php", + "generated/pcntl.php", + "generated/pcre.php", + "generated/pgsql.php", + "generated/posix.php", + "generated/ps.php", + "generated/pspell.php", + "generated/readline.php", + "generated/rnp.php", + "generated/rpminfo.php", + "generated/rrd.php", + "generated/sem.php", + "generated/session.php", + "generated/shmop.php", + "generated/sockets.php", + "generated/sodium.php", + "generated/solr.php", + "generated/spl.php", + "generated/sqlsrv.php", + "generated/ssdeep.php", + "generated/ssh2.php", + "generated/stream.php", + "generated/strings.php", + "generated/swoole.php", + "generated/uodbc.php", + "generated/uopz.php", + "generated/url.php", + "generated/var.php", + "generated/xdiff.php", + "generated/xml.php", + "generated/xmlrpc.php", + "generated/yaml.php", + "generated/yaz.php", + "generated/zip.php", + "generated/zlib.php" + ], + "classmap": [ + "lib/DateTime.php", + "lib/DateTimeImmutable.php", + "lib/Exceptions/", + "generated/Exceptions/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "PHP core functions that throw exceptions instead of returning FALSE on error", + "support": { + "issues": "https://github.com/thecodingmachine/safe/issues", + "source": "https://github.com/thecodingmachine/safe/tree/v3.4.0" + }, + "funding": [ + { + "url": "https://github.com/OskarStark", + "type": "github" + }, + { + "url": "https://github.com/shish", + "type": "github" + }, + { + "url": "https://github.com/silasjoisten", + "type": "github" + }, + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2026-02-04T18:08:13+00:00" + }, { "name": "tijsverkoyen/css-to-inline-styles", "version": "v2.4.0", diff --git a/database/seeders/RoleSeeder.php b/database/seeders/RoleSeeder.php index fb117268..67c7ef31 100644 --- a/database/seeders/RoleSeeder.php +++ b/database/seeders/RoleSeeder.php @@ -11,7 +11,7 @@ class RoleSeeder extends Seeder public function run(): void { - $role1 = Role::create(['name'=>'Admin']); + $role1 = Role::create(['name'=>'Administrador']); $role2 = Role::create(['name'=>'Programador']); $role3 = Role::create(['name'=>'Promotor']); $role4 = Role::create(['name'=>'Alumno']); @@ -74,6 +74,11 @@ class RoleSeeder extends Seeder Permission::create(['name'=>'alumno.edit','description'=>'Editar alumnos'])->syncRoles([$role1,$role3,$role5]); Permission::create(['name'=>'alumno.destroy','description'=>'Eliminar alumnos'])->syncRoles([$role1]); + Permission::create(['name'=>'prospecto.index','description'=>'Ver prospectos'])->syncRoles([$role1,$role3]); + Permission::create(['name'=>'prospecto.create','description'=>'Crear prospectos'])->syncRoles([$role1,$role3]); + Permission::create(['name'=>'prospecto.edit','description'=>'Editar prospectos'])->syncRoles([$role1,$role3]); + Permission::create(['name'=>'prospecto.destroy','description'=>'Eliminar prospectos'])->syncRoles([$role1]); + Permission::create(['name'=>'area.index','description'=>'Ver areas'])->syncRoles([$role1]); Permission::create(['name'=>'area.create','description'=>'Crear areas'])->syncRoles([$role1]); Permission::create(['name'=>'area.edit','description'=>'Editar areas'])->syncRoles([$role1]); @@ -98,6 +103,7 @@ class RoleSeeder extends Seeder Permission::create(['name'=>'plane.create','description'=>'Crear planes de pago'])->syncRoles([$role1]); Permission::create(['name'=>'plane.edit','description'=>'Editar planes de pago'])->syncRoles([$role1]); Permission::create(['name'=>'plane.destroy','description'=>'Eliminar planes de pago'])->syncRoles([$role1]); + Permission::create(['name'=>'pagar','description'=>'Pagar'])->syncRoles([$role4]); Permission::create(['name'=>'concepto.index','description'=>'Ver conceptos'])->syncRoles([$role1]); Permission::create(['name'=>'concepto.create','description'=>'Crear conceptos'])->syncRoles([$role1]); @@ -126,7 +132,7 @@ class RoleSeeder extends Seeder Permission::create(['name'=>'pago.edit','description'=>'Editar pagos'])->syncRoles([$role1]); Permission::create(['name'=>'pago.destroy','description'=>'Eliminar pagos'])->syncRoles([$role1,$role3]); - Permission::create(['name'=>'grupo.index','description'=>'Ver grupos'])->syncRoles([$role1,$role4,$role5,$role6,$role7]); + Permission::create(['name'=>'grupo.index','description'=>'Ver grupos'])->syncRoles([$role1,$role5,$role6,$role7]); Permission::create(['name'=>'grupo.create','description'=>'Crear grupos'])->syncRoles([$role1,$role5]); Permission::create(['name'=>'grupo.edit','description'=>'Editar grupos'])->syncRoles([$role1,$role5,$role6]); Permission::create(['name'=>'grupo.destroy','description'=>'Eliminar grupos'])->syncRoles([$role1,$role5]); @@ -141,6 +147,23 @@ class RoleSeeder extends Seeder Permission::create(['name'=>'documentoTipo.edit','description'=>'Editar documentación'])->syncRoles([$role1]); Permission::create(['name'=>'documentoTipo.destroy','description'=>'Eliminar documentación'])->syncRoles([$role1]); + Permission::create(['name'=>'evaluaciones.index','description'=>'Ver evaluaciones'])->syncRoles([$role1,$role7,$role4]); + Permission::create(['name'=>'evaluaciones.create','description'=>'Crear evaluaciones'])->syncRoles([$role1]); + Permission::create(['name'=>'evaluaciones.edit','description'=>'Editar evaluaciones'])->syncRoles([$role1]); + Permission::create(['name'=>'evaluaciones.destroy','description'=>'Eliminar evaluaciones'])->syncRoles([$role1]); + Permission::create(['name'=>'evaluaciones.responder','description'=>'responder evaluaciones'])->syncRoles([$role4,$role7]); + + Permission::create(['name'=>'anuncios.index','description'=>'Ver anuncios'])->syncRoles([$role1]); + Permission::create(['name'=>'anuncios.create','description'=>'Crear anuncios'])->syncRoles([$role1]); + Permission::create(['name'=>'anuncios.edit','description'=>'Editar anuncios'])->syncRoles([$role1]); + Permission::create(['name'=>'anuncios.destroy','description'=>'Eliminar anuncios'])->syncRoles([$role1]); + + Permission::create(['name'=>'tareas.index','description'=>'Ver tareas'])->syncRoles([$role7]); + Permission::create(['name'=>'tareas.create','description'=>'Crear tareas'])->syncRoles([$role7]); + Permission::create(['name'=>'tareas.edit','description'=>'Editar tareas'])->syncRoles([$role7]); + Permission::create(['name'=>'tareas.destroy','description'=>'Eliminar tareas'])->syncRoles([$role7]); + Permission::create(['name'=>'tareas.entrega','description'=>'Entregar tareas'])->syncRoles([$role4]); + Permission::create(['name'=>'comunidad','description'=>'Comunidad'])->syncRoles([$role1]); Permission::create(['name'=>'amigos','description'=>'Amistades'])->syncRoles([$role1]); Permission::create(['name'=>'mensajes','description'=>'Mensajería'])->syncRoles([$role1]); diff --git a/database/seeders/UserSeeder.php b/database/seeders/UserSeeder.php index 5bc237cc..62c5b03e 100644 --- a/database/seeders/UserSeeder.php +++ b/database/seeders/UserSeeder.php @@ -23,7 +23,7 @@ class UserSeeder extends Seeder 'password' => bcrypt('6686'), 'code'=>1, 'status'=>1 - ])->assignRole('Admin'); + ])->assignRole('Administrador'); $user->plantelUsuarios()->sync(3); diff --git a/resources/views/alumno/edit.blade.php b/resources/views/alumno/edit.blade.php index 91f50bc4..13e3fa81 100644 --- a/resources/views/alumno/edit.blade.php +++ b/resources/views/alumno/edit.blade.php @@ -27,7 +27,138 @@ + + +
| Id | +Foto | Alumno | Campus | Status | @@ -86,10 +89,18 @@{{ $alumno->id }} | + +
+ @foreach($alumno->alumnos as $user)
+ |
@foreach($alumno->alumnos as $user) - {{ $user->name }} + {{ $user->name }} {{ $user->apellidoPaterno }} {{ $user->apellidoMaterno }} @endforeach | @@ -118,24 +129,20 @@ -+ |
@can('alumno.edit')
-
Edit
-
@endcan
@can('alumno.destroy')
-
-
@endcan
|
@@ -151,8 +158,7 @@
|---|
Sin respuestas registradas.
+ @endforelse + @else + @foreach($item['por_docente'] as $pd) +Sin respuestas.
+ @endforelse ++ + {{ $item['total'] }} respuesta(s) +
+ @if(isset($item['promedio'])) +Sin respuestas registradas.
+ @endif + + {{-- Gráficas por docente --}} + @else + @if(empty($item['por_docente'])) +Sin respuestas registradas.
+ @else ++ {{ $pd['total'] }} respuesta(s) +
+ @else +Sin respuestas.
+ @endif +| + Participantes + {{ $totalSubmissions }} + | ++ Preguntas + {{ count($datos) }} + | ++ Estado + {{ ucfirst($evaluacion->status) }} + | ++ Tipo + {{ $evaluacion->tipo?->nombre }} + | +
+ Esta evaluación aún no tiene respuestas registradas. +
+@endif + +{{-- PREGUNTAS --}} +@foreach($datos as $i => $item) +@php $pregunta = $item['pregunta']; @endphp + +Sin respuestas registradas.
+ @endforelse + @else + @foreach($item['por_docente'] as $pd) +Sin respuestas.
+ @endforelse +|
+ @if(isset($item['chart_img']))
+ Gráfica no disponible
+ @endif
+ |
+
+ Respuestas
+ {{ $item['total'] }}
+ @if(isset($item['promedio']))
+
+
+ @endif
+ Promedio
+ {{ $item['promedio'] }}
+ / {{ str_contains($item['tipo'], '10') ? '10' : '5' }}
+ |
+
Sin respuestas registradas.
+ @endif + + {{-- Gráficas por docente (tabla 2 columnas) --}} + @else + + @if(empty($item['por_docente'])) +Sin respuestas registradas.
+ @else + @php + $chunks = array_chunk($item['por_docente'], 2); + @endphp + @foreach($chunks as $pair) +|
+
+
+
+ {{ $pd['docente_nombre'] }}
+ @if(isset($pd['promedio']))
+ ·
+
+ Prom: {{ $pd['promedio'] }}
+
+ @endif
+
+ @if($pd['total'] > 0)
+ @if(isset($pd['chart_img']))
+ Gráfica no disponible
+ @endif
+ + {{ $pd['total'] }} respuesta(s) + + @else +Sin respuestas. + @endif + |
+ @endforeach
+ @if(count($pair) === 1)
+ + @endif + |