Guardar cambios consolidar grupos docentes y clave de grupo

This commit is contained in:
2026-04-24 18:33:37 -06:00
parent 2d73e8a2b0
commit 2b072f7d55
10 changed files with 205 additions and 125 deletions
+43 -8
View File
@@ -66,15 +66,40 @@ class GrupoController extends Controller
public function store(Request $request) public function store(Request $request)
{ {
$nivel = Plan::where('id',$request->plan_id)->first(); $plan = Plan::with('nivelRel')->where('id', $request->plan_id)->first();
$plantel = Plantel::find($request->plantel);
$turno = Turno::find($request->turno);
$nomenclatura = strtoupper(substr($plantel->nomenclatura, 0, 3));
$nivel = strtoupper(substr($plan->nivelRel->name, 0, 1));
$turnoLetra = strtoupper(substr($turno->name, 0, 1));
$planId = $request->plan_id;
$anioMes = now()->format('Ym');
$claveBase = "{$nomenclatura}-{$nivel}-{$turnoLetra}-{$planId}-{$anioMes}";
$sufijos = range('A', 'Z');
$clave = $claveBase;
if (Grupo::where('clave', $claveBase)->exists()) {
foreach ($sufijos as $sufijo) {
$claveConSufijo = "{$claveBase}-{$sufijo}";
if (!Grupo::where('clave', $claveConSufijo)->exists()) {
$clave = $claveConSufijo;
break;
}
}
}
$grupo = Grupo::create([ $grupo = Grupo::create([
'plan_id' => $request->plan_id, 'plan_id' => $request->plan_id,
'ciclo' => 1, 'ciclo' => 1,
'nivel' => $nivel->nivel, 'nivel' => $plan->nivel,
'plantel' => $request->plantel, 'plantel' => $request->plantel,
'turno' => $request->turno, 'turno' => $request->turno,
'status'=>1 'status' => 1,
]); 'clave' => $clave,
]);
return redirect()->route('grupo.edit',$grupo)->with('info','El grupo se creo correctamente'); return redirect()->route('grupo.edit',$grupo)->with('info','El grupo se creo correctamente');
@@ -104,6 +129,15 @@ class GrupoController extends Controller
}); });
$ciclos = Plan::where('id',$grupo->plan_id)->first(); $ciclos = Plan::where('id',$grupo->plan_id)->first();
$grupo->load(['alumnos' => function ($query) {
$query->join('alumno_user', 'alumnos.id', '=', 'alumno_user.alumno_id')
->join('users', 'alumno_user.user_id', '=', 'users.id')
->orderBy('users.apellidoPaterno')
->orderBy('users.apellidoMaterno')
->orderBy('users.name')
->select('alumnos.*');
}]);
return view('grupo.edit',compact(['grupo','turnos','plans','planteles','ciclos'])); return view('grupo.edit',compact(['grupo','turnos','plans','planteles','ciclos']));
} }
@@ -140,6 +174,7 @@ class GrupoController extends Controller
return redirect()->route('grupo.edit',$grupo); return redirect()->route('grupo.edit',$grupo);
}else{ }else{
$grupo->alumnos()->detach(); $grupo->alumnos()->detach();
$grupo->docentes()->detach();
$grupo->delete(); $grupo->delete();
session()->flash('swal',[ session()->flash('swal',[
'icon'=>'success', 'icon'=>'success',
+13 -24
View File
@@ -57,32 +57,21 @@ class GrupoAlumno extends Component
$plantelesUsuario = $user->plantelUsuarios->pluck('id'); $plantelesUsuario = $user->plantelUsuarios->pluck('id');
$alumnos = Alumno::query() $alumnos = Alumno::query()
->with(['alumnos.plantelUsuarios', 'statusAlumno', 'plan.carreraRel','nivelRel']) ->with(['usuario', 'statusAlumno', 'plan.carreraRel', 'nivelRel'])
->where('status', 2) // ✅ Cambia el 2 por el status correcto
->whereIn('status', [2, 3]) // inscrito o alumno
->where('nivel', $grupo->nivel) ->where('nivel', $grupo->nivel)
->whereDoesntHave('grupos')
->whereDoesntHave('grupos') ->whereHas('usuario', function ($q) use ($plantelesUsuario) {
$q->whereHas('plantelUsuarios', function ($q2) use ($plantelesUsuario) {
->whereHas('alumnos.plantelUsuarios', function ($q) use ($plantelesUsuario) { $q2->whereIn('plantel_user.plantel_id', $plantelesUsuario);
$q->whereIn('plantels.id', $plantelesUsuario); });
}) })
->orderByRaw("CASE WHEN plan_id = {$grupo->plan_id} THEN 0 ELSE 1 END")
->orderByRaw(" ->orderByRaw("CASE WHEN nivel = {$grupo->nivel} THEN 0 ELSE 1 END")
CASE ->get();
WHEN plan_id = {$grupo->plan_id} THEN 0
ELSE 1
END
")
->orderByRaw("
CASE
WHEN nivel = {$grupo->nivel} THEN 0
ELSE 1
END"
)
->get();
return view('livewire.grupo-alumno', [ return view('livewire.grupo-alumno', [
'alumnos' => $alumnos, 'alumnos' => $alumnos,
+6 -8
View File
@@ -71,25 +71,23 @@ $this->materiasSelect = Material::whereHas('carreraMaterial', function($query) u
icon: 'success', icon: 'success',
title: '¡Listo!', title: '¡Listo!',
text: 'Docente asignado correctamente', text: 'Docente asignado correctamente',
timer: 2000, timer: 3000,
confirm: false, confirm: false,
closeModal: true, closeModal: true,
reload: true
); );
} }
public function eliminar($docenteId) public function eliminar($docenteId, $materiaId)
{ {
$this->grupo->docentes()->detach($docenteId); $this->grupo->docentes()->wherePivot('materia_id', $materiaId)->detach($docenteId);
$this->dispatch('swal', $this->dispatch('swal',
icon: 'success', icon: 'success',
title: '¡Eliminado!', title: 'Eliminado',
text: 'Docente removido correctamente', text: 'Docente removido de la materia correctamente',
timer: 2000, timer: 2000,
confirm: false, confirm: false,
closeModal: false, closeModal: true,
reload: true
); );
} }
+1 -1
View File
@@ -5,7 +5,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Material extends Model class Tarea extends Model
{ {
protected $guarded = []; protected $guarded = [];
@@ -11,6 +11,7 @@ return new class extends Migration
Schema::create('grupos', function (Blueprint $table) { Schema::create('grupos', function (Blueprint $table) {
$table->id(); $table->id();
$table->unsignedBigInteger('plan_id'); $table->unsignedBigInteger('plan_id');
$table->string('clave')->unique();
$table->unsignedBigInteger('ciclo'); $table->unsignedBigInteger('ciclo');
$table->unsignedBigInteger('nivel'); $table->unsignedBigInteger('nivel');
$table->unsignedBigInteger('plantel'); $table->unsignedBigInteger('plantel');
+2
View File
@@ -16,6 +16,7 @@ class GrupoSeeder extends Seeder
$grupo = Grupo::create([ $grupo = Grupo::create([
'plan_id'=> 1, 'plan_id'=> 1,
'ciclo'=> 1, 'ciclo'=> 1,
'clave'=>"WASDWASD",
'nivel'=> 1, 'nivel'=> 1,
'plantel'=> 3, 'plantel'=> 3,
'turno'=> 1, 'turno'=> 1,
@@ -32,6 +33,7 @@ class GrupoSeeder extends Seeder
$grupo2 = Grupo::create([ $grupo2 = Grupo::create([
'plan_id'=> 2, 'plan_id'=> 2,
'ciclo'=> 1, 'ciclo'=> 1,
'clave'=>"WASDWASD2",
'nivel'=> 2, 'nivel'=> 2,
'plantel'=> 3, 'plantel'=> 3,
'turno'=> 2, 'turno'=> 2,
+1 -1
View File
@@ -37,7 +37,7 @@ class UserSeeder extends Seeder
'password' => bcrypt('6686'), 'password' => bcrypt('6686'),
'code'=>1, 'code'=>1,
'status'=>1 'status'=>1
])->assignRole('Promotor'); ])->assignRole('Control escolar');
$user1->plantelUsuarios()->sync(3); $user1->plantelUsuarios()->sync(3);
+35 -69
View File
@@ -26,8 +26,8 @@
@method('PUT') @method('PUT')
@csrf @csrf
<div class="mb-3"> <div class="mb-3">
<label>Seleccionar Carrera</label> <label>Carrera</label>
<select class="form-control" name="plan_id"> <select class="form-control" name="plan_id" disabled>
<option value="">Seleccionar plan</option> <option value="">Seleccionar plan</option>
@foreach ($plans as $nivel => $grupos) @foreach ($plans as $nivel => $grupos)
<optgroup label="{{ $nivel }}"> <optgroup label="{{ $nivel }}">
@@ -43,7 +43,12 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label>Seleccionar ciclo</label> <label>Clave</label>
<input type="text" class="form-control" name="clave" value="{{ $grupo->clave }}" disabled>
</div>
<div class="mb-3">
<label>ciclo</label>
<select class="form-control" name="ciclo"> <select class="form-control" name="ciclo">
<option value="0">Seleccionar ciclo</option> <option value="0">Seleccionar ciclo</option>
@for ($i=1 ; $i <= $ciclos->duracion ; $i++) @for ($i=1 ; $i <= $ciclos->duracion ; $i++)
@@ -53,7 +58,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label>Seleccionar turno</label> <label>turno</label>
<select class="form-control" name="turno"> <select class="form-control" name="turno">
<option value="0">Seleccionar turno</option> <option value="0">Seleccionar turno</option>
@foreach ($turnos as $turno) @foreach ($turnos as $turno)
@@ -65,8 +70,8 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label>Seleccionar plantel</label> <label>plantel</label>
<select class="form-control" name="plantel"> <select class="form-control" name="plantel" disabled>
<option value="0">Seleccionar plantel</option> <option value="0">Seleccionar plantel</option>
@foreach ($planteles as $plantel) @foreach ($planteles as $plantel)
<option value="{{ $plantel->id }}" {{ $plantel->id == $grupo->plantel ? 'selected' : '' }}> <option value="{{ $plantel->id }}" {{ $plantel->id == $grupo->plantel ? 'selected' : '' }}>
@@ -77,7 +82,7 @@
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label>Seleccionar status</label> <label>status</label>
<select class="form-control" name="status"> <select class="form-control" name="status">
<option value="1" {{ 1 == $grupo->status ? 'selected' : '' }}>Activo</option> <option value="1" {{ 1 == $grupo->status ? 'selected' : '' }}>Activo</option>
<option value="0" {{ 0 == $grupo->status ? 'selected' : '' }}>Inactivo</option> <option value="0" {{ 0 == $grupo->status ? 'selected' : '' }}>Inactivo</option>
@@ -113,36 +118,41 @@
<div class="card-header py-3"> <div class="card-header py-3">
<div class="d-sm-flex align-items-center justify-content-between"> <div class="d-sm-flex align-items-center justify-content-between">
<h6 class="m-0 font-weight-bold text-primary">Alumnos</h6> <h6 class="m-0 font-weight-bold text-primary">Alumnos</h6>
@can('grupo.create') @hasrole('Control escolar')
<button onclick="Livewire.dispatch('create-grupo')" <button onclick="Livewire.dispatch('create-grupo')"
class="d-sm-inline-block btn btn-sm btn-success shadow-sm"> class="d-sm-inline-block btn btn-sm btn-success shadow-sm">
<i class="fas fa-plus fa-sm text-white-50"></i> Agregar alumno <i class="fas fa-plus fa-sm text-white-50"></i> Agregar alumno
</button> </button>
@endcan @endhasrole
</div> </div>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="container-fluid"> <div class="container-fluid">
<table id="tcont2" class="table table-striped table-bordered nowrap table-hover" style="width:100%;"> <table id="tcont" class="table table-striped table-bordered nowrap table-hover" style="width:100%;">
<thead> <thead>
<tr> <tr>
<th>Id</th> {{-- <th>Id</th> --}}
<th>Nombre</th> <th>Nombre</th>
<th>Opciones</th> @hasrole('Control escolar')
<th>Opciones</th>
@endhasrole
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@forelse ($grupo->alumnos as $alumno) @forelse ($grupo->alumnos as $alumno)
<tr> <tr>
<td>{{$alumno->id}}</td> {{-- <td>{{$alumno->id}}</td> --}}
<td> <td>
{{$alumno->usuario->first()?->name}}
{{$alumno->usuario->first()?->apellidoPaterno}} {{$alumno->usuario->first()?->apellidoPaterno}}
{{$alumno->usuario->first()?->apellidoMaterno}} {{$alumno->usuario->first()?->apellidoMaterno}}
{{$alumno->usuario->first()?->name}}
</td> </td>
@hasrole('Control escolar')
<td> <td>
@can('grupo.destroy')
<form class="delete-form" <form class="delete-form"
action="{{route('grupo.destroy',$grupo->id)}}" action="{{route('grupo.destroy',$grupo->id)}}"
method="POST"> method="POST">
@@ -150,15 +160,17 @@
@method('DELETE') @method('DELETE')
<input class="d-none" name="alumno" value="{{$alumno->id}}"> <input class="d-none" name="alumno" value="{{$alumno->id}}">
<button type="submit" style="border:none; background:none;"> <button type="submit" style="border:none; background:none;">
<i class="fa-solid fa-circle-xmark fa-xl" <i class="fa-regular fa-circle-user-circle-minus fa-2xl" style="color: rgb(255, 0, 0);"></i>
style="color:#e66565;"></i>
</button> </button>
</form> </form>
@endcan
</td> </td>
@endhasrole
</tr> </tr>
@empty @empty
<tr><td colspan="3">Sin alumnos.</td></tr> <tr>Sin alumnos.</tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
@@ -185,50 +197,4 @@ document.addEventListener('livewire:init', () => {
}); });
}); });
</script> </script>
<script> new DataTable('#tcont2', {
responsive: true,
language: {
"decimal": "",
"emptyTable": "No hay información",
"info": "Mostrando _START_ a _END_ de _TOTAL_ Entradas",
"infoEmpty": "Mostrando 0 to 0 of 0 Entradas",
"infoFiltered": "(Filtrado de _MAX_ total entradas)",
"infoPostFix": "",
"thousands": ",",
"lengthMenu": "Mostrar _MENU_ Entradas",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"zeroRecords": "Sin resultados encontrados"
},
lengthMenu: [
[10, 25, 50, -1],
['10 filas', '25 filas', '50 filas', 'Todos']
],
layout: {
topStart: {
buttons: ['colvis', 'pageLength']
},
topEnd: ['search'],
bottomStart: [{
buttons: [{
extend: 'excel',
title: 'Excel'
},
{
extend: 'pdfHtml5',
title: 'PDF'
},
{
extend: 'print'
}
]
}, 'info'],
}
});
$("#tcont2_wrapper").removeClass("form-inline");
$("#tcont2_wrapper").addClass("w-100"); </script>
@endsection @endsection
+4
View File
@@ -26,6 +26,7 @@
<tr> <tr>
<th>Id</th> <th>Id</th>
<th>Nombre</th> <th>Nombre</th>
<th>Clave</th>
<th>Opciones</th> <th>Opciones</th>
</tr> </tr>
</thead> </thead>
@@ -35,6 +36,9 @@
<td>{{$grupo->id}}</td> <td>{{$grupo->id}}</td>
<td><a href="{{route('grupo.show',$grupo->id)}}">{{ $grupo->plan->carreraRel->name }} <td><a href="{{route('grupo.show',$grupo->id)}}">{{ $grupo->plan->carreraRel->name }}
({{ $grupo->plan->name }}-{{ $grupo->plan->rvoe }})</a></td> ({{ $grupo->plan->name }}-{{ $grupo->plan->rvoe }})</a></td>
<td>{{$grupo->clave}}</td>
<td class="row mx-auto"> <td class="row mx-auto">
@can('grupo.edit') @can('grupo.edit')
<div class="col-sm-6 col-md-6"><a class="btn btn-primary" href="{{route('grupo.edit',$grupo->id)}}">Edit</a></div> <div class="col-sm-6 col-md-6"><a class="btn btn-primary" href="{{route('grupo.edit',$grupo->id)}}">Edit</a></div>
@@ -53,14 +53,17 @@
</div> </div>
{{-- Tabla docentes asignados --}} {{-- Tabla docentes asignados --}}
<table id="tcont" class="table table-striped table-bordered table-hover" style="width:100%;"> <table id="tcont2" class="table table-striped table-bordered table-hover" style="width:100%;">
<thead> <thead>
<tr> <tr>
<th>Id</th> <th>Id</th>
<th>Materia</th> <th>Materia</th>
<th>Ciclo</th> <th>Ciclo</th>
<th>Docente asignado</th> <th>Docente asignado</th>
@hasrole('Coordinación académica')
<th>Opciones</th> <th>Opciones</th>
@endhasrole
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@@ -80,26 +83,110 @@
<span class="badge badge-secondary">Sin docente</span> <span class="badge badge-secondary">Sin docente</span>
@endif @endif
</td> </td>
<td>
@if($docenteAsignado)
@can('grupo.destroy') @hasrole('Coordinación académica')
<button wire:click="eliminar({{ $docenteAsignado->id }})" <td>
style="border:none; background:none;"> @if($docenteAsignado)
<i class="fa-solid fa-circle-xmark fa-xl" style="color:#e66565;"></i> <button
</button> onclick="confirmarEliminar({{ $docenteAsignado->id }}, {{ $materia->id }}, '{{ $usr?->name }} {{ $usr?->apellidoPaterno }}', '{{ $materia->name }}')"
@endcan style="border:none; background:none;">
@endif <i class="fa-regular fa-circle-user-circle-minus fa-2xl" style="color: rgb(255, 0, 0);"></i>
</td> </button>
@endif
</td>
@endhasrole
</tr> </tr>
@empty @empty
<tr><td colspan="5">Sin materias en este plan.</td></tr> <tr>Sin materias en este plan.</tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
@push('scripts') @push('scripts')
<script> <script>
document.addEventListener('DOMContentLoaded', initTable);
document.addEventListener('livewire:init', () => {
// ✅ Este hook es el correcto para Livewire v3
Livewire.hook('commit', ({ component, succeed }) => {
succeed(() => {
setTimeout(() => initTable(), 50);
});
});
});
function confirmarEliminar(docenteId, materiaId, nombreDocente, nombreMateria) {
Swal.fire({
title: "¿Eliminar docente?",
html: `¿Deseas quitar a <b>${nombreDocente}</b> de <b>${nombreMateria}</b>?`,
icon: "warning",
showCancelButton: true,
confirmButtonText: "Sí, eliminar",
cancelButtonText: "Cancelar",
confirmButtonColor: '#e74a3b',
}).then((result) => {
if (result.isConfirmed) {
@this.eliminar(docenteId, materiaId);
}
});
}
function initTable() {
// ✅ Destroy correcto
if ($.fn.DataTable.isDataTable('#tcont2')) {
$('#tcont2').DataTable().destroy();
$('#tcont2').empty(); // limpia el DOM completamente
}
new DataTable('#tcont2', {
retrieve: true, // ✅ evita el error de reinicialización
responsive: true,
order: [],
language: {
"decimal": "",
"emptyTable": "No hay información",
"info": "Mostrando _START_ a _END_ de _TOTAL_ Entradas",
"infoEmpty": "Mostrando 0 to 0 of 0 Entradas",
"infoFiltered": "(Filtrado de _MAX_ total entradas)",
"infoPostFix": "",
"thousands": ",",
"lengthMenu": "Mostrar _MENU_ Entradas",
"loadingRecords": "Cargando...",
"processing": "Procesando...",
"search": "Buscar:",
"zeroRecords": "Sin resultados encontrados"
},
lengthMenu: [
[10, 25, 50, -1],
['10 filas', '25 filas', '50 filas', 'Todos']
],
layout: {
topStart: {
buttons: ['colvis', 'pageLength']
},
topEnd: ['search'],
bottomStart: [{
buttons: [
{ extend: 'excel', title: 'Excel' },
{ extend: 'pdfHtml5', title: 'PDF' },
{ extend: 'print' }
]
}, 'info'],
}
});
$("#tcont2_wrapper").removeClass("form-inline");
$("#tcont2_wrapper").addClass("w-100");
}
document.addEventListener('livewire:init', () => { document.addEventListener('livewire:init', () => {
Livewire.on('open-modal-docente', () => { Livewire.on('open-modal-docente', () => {
$('#modalDocente').modal('show'); $('#modalDocente').modal('show');
@@ -108,7 +195,5 @@
$('#modalDocente').modal('hide'); $('#modalDocente').modal('hide');
}); });
}); });
</script> </script>
@endpush @endpush