archivo de configuracion de nginx agregado para el puerto 9000

This commit is contained in:
2026-05-02 16:57:29 -06:00
parent 2af36ea272
commit 881e67fb12
33 changed files with 2035 additions and 245 deletions
+108
View File
@@ -0,0 +1,108 @@
<?php
namespace App\Http\Controllers;
use App\Models\Anuncio;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class AnuncioController extends Controller
{
public function index()
{
$anuncios = Anuncio::with('author')->latest()->paginate(15);
return view('anuncio.index', compact('anuncios'));
}
public function create()
{
return view('anuncio.create', [
'types' => Anuncio::TYPES,
'roles' => Anuncio::ROLES,
]);
}
public function store(Request $request)
{
$data = $request->validate([
'title' => 'required|string|max:200',
'body' => 'nullable|string|max:3000',
'image' => 'nullable|image|max:5120',
'type' => 'required|in:info,promocion,mantenimiento,aviso',
'audience' => 'required|array|min:1',
'audience.*' => 'string',
'expires_at' => 'nullable|date|after:now',
'active' => 'boolean',
]);
$data['user_id'] = auth()->id();
$data['active'] = $request->boolean('active', true);
$data['expires_at'] = $request->expires_at ?: null;
if ($request->hasFile('image')) {
$data['image_path'] = $request->file('image')->store('anuncios', 'public');
}
unset($data['image']);
Anuncio::create($data);
return redirect()->route('anuncio.index')->with('success', 'Anuncio creado correctamente.');
}
public function edit(Anuncio $anuncio)
{
return view('anuncio.edit', [
'anuncio' => $anuncio,
'types' => Anuncio::TYPES,
'roles' => Anuncio::ROLES,
]);
}
public function update(Request $request, Anuncio $anuncio)
{
$data = $request->validate([
'title' => 'required|string|max:200',
'body' => 'nullable|string|max:3000',
'image' => 'nullable|image|max:5120',
'type' => 'required|in:info,promocion,mantenimiento,aviso',
'audience' => 'required|array|min:1',
'audience.*' => 'string',
'expires_at' => 'nullable|date',
'active' => 'boolean',
]);
$data['active'] = $request->boolean('active', true);
$data['expires_at'] = $request->expires_at ?: null;
if ($request->hasFile('image')) {
if ($anuncio->image_path) {
Storage::disk('public')->delete($anuncio->image_path);
}
$data['image_path'] = $request->file('image')->store('anuncios', 'public');
} elseif ($request->boolean('remove_image') && $anuncio->image_path) {
Storage::disk('public')->delete($anuncio->image_path);
$data['image_path'] = null;
}
unset($data['image']);
$anuncio->update($data);
return redirect()->route('anuncio.index')->with('success', 'Anuncio actualizado.');
}
public function destroy(Anuncio $anuncio)
{
if ($anuncio->image_path) {
Storage::disk('public')->delete($anuncio->image_path);
}
$anuncio->delete();
return back()->with('success', 'Anuncio eliminado.');
}
public function toggleActive(Anuncio $anuncio)
{
$anuncio->update(['active' => !$anuncio->active]);
return back()->with('success', $anuncio->active ? 'Anuncio activado.' : 'Anuncio desactivado.');
}
}
+19 -4
View File
@@ -10,12 +10,27 @@ use Illuminate\Support\Facades\Storage;
class ChatController extends Controller
{
// Lista de amigos con quienes puedo chatear
// Lista de conversaciones (amigos + historial con no-amigos)
public function index()
{
$me = auth()->user();
$friends = User::whereIn('id', $me->friendIds())->orderBy('name')->get();
return view('chat.index', compact('friends'));
$me = auth()->user();
// IDs de usuarios con quienes hay mensajes intercambiados
$sentTo = Message::where('user_id', $me->id)->pluck('receiver_id');
$receivedFrom = Message::where('receiver_id', $me->id)->pluck('user_id');
$conversationIds = $sentTo->merge($receivedFrom)->unique()->values();
// Amigos (con o sin historial de mensajes)
$friendIds = collect($me->friendIds());
$allIds = $conversationIds->merge($friendIds)->unique()->values();
$users = User::whereIn('id', $allIds)->orderBy('name')->get()
->map(function (User $user) use ($me) {
$user->is_friend = $me->isFriendWith($user);
return $user;
});
return view('chat.index', compact('users'));
}
// Mensajes entre yo y otro usuario
@@ -0,0 +1,80 @@
<?php
namespace App\Http\Controllers;
use App\Models\Alumno;
use App\Models\Entrega;
use App\Models\Tarea;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
class EntregaController extends Controller
{
public function index()
{
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->first();
if (!$alumno) {
return view('tarea.mis_tareas', ['tareas' => collect(), 'entregasMap' => collect()]);
}
$gruposIds = $alumno->grupos()->pluck('grupos.id');
$tareas = Tarea::whereIn('grupo_id', $gruposIds)
->with(['material', 'grupo'])
->orderByDesc('created_at')
->get();
$entregasMap = Entrega::where('alumno_id', $alumno->id)
->whereIn('tarea_id', $tareas->pluck('id'))
->get()
->keyBy('tarea_id');
return view('tarea.mis_tareas', compact('tareas', 'entregasMap', 'alumno'));
}
public function store(Request $request, Tarea $tarea)
{
$request->validate([
'archivo' => 'nullable|file|max:20480',
'texto' => 'nullable|string|max:5000',
]);
if (!$request->hasFile('archivo') && !filled($request->texto)) {
return back()->withErrors(['entrega' => 'Debes escribir un texto o adjuntar un archivo.']);
}
$alumno = Alumno::whereHas('alumnos', fn($q) => $q->where('id', Auth::id()))->firstOrFail();
$entrega = Entrega::where('tarea_id', $tarea->id)
->where('alumno_id', $alumno->id)
->first();
if (!$entrega) {
$entrega = Entrega::create([
'tarea_id' => $tarea->id,
'alumno_id' => $alumno->id,
'status' => 'sin_subir',
]);
}
if ($entrega->status !== 'sin_subir') {
return back()->with('error', 'Ya entregaste esta tarea y no puedes volver a subirla.');
}
$path = null;
if ($request->hasFile('archivo')) {
// Almacena en disco local (privado) igual que el FileController espera
$path = $request->file('archivo')->store("entregas/{$tarea->id}");
}
$entrega->update([
'archivo' => $path,
'texto' => $request->texto,
'status' => 'pendiente',
]);
return back()->with('info', 'Tarea enviada correctamente. Queda pendiente de calificación.');
}
}
+45
View File
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Alumno;
use App\Models\Docente;
use App\Models\Grupo;
use App\Models\Plan;
use App\Models\Plane;
@@ -11,6 +12,7 @@ use App\Models\Turno;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
class GrupoController extends Controller
{
@@ -38,12 +40,55 @@ class GrupoController extends Controller
$query->whereIn('plantel_id', $plantelesIds);
})
->get();
} elseif ($user->hasRole('Docente')) {
$grupos = Grupo::with('plan')
->whereHas('docentes', function ($query) use ($user) {
$query->whereHas('docentes', function ($q) use ($user) {
$q->where('users.id', $user->id);
});
})
->get();
}
return view('grupo.index', compact('grupos'));
}
public function verMaterias(Grupo $grupo)
{
$asignaciones = DB::table('docente_grupo')
->where('docente_grupo.grupo_id', $grupo->id)
->join('materials', 'docente_grupo.materia_id', '=', 'materials.id')
->join('docentes', 'docente_grupo.docente_id', '=', 'docentes.id')
->leftJoin('docente_user', 'docentes.id', '=', 'docente_user.docente_id')
->leftJoin('users', 'docente_user.user_id', '=', 'users.id')
->select(
'materials.id as material_id',
'materials.name as material_name',
'materials.clave as material_clave',
'materials.ciclo',
'materials.horas',
'docentes.id as docente_id',
'users.name as docente_nombre',
'users.apellidoPaterno',
'users.apellidoMaterno'
)
->get()
->unique('material_id');
return view('grupo.ver_materias', compact('grupo', 'asignaciones'));
}
public function administrar(Grupo $grupo)
{
$user = Auth::user();
$docente = Docente::whereHas('docentes', fn($q) => $q->where('id', $user->id))->firstOrFail();
$materiales = $docente->materias()->wherePivot('grupo_id', $grupo->id)->get();
return view('grupo.administrar', compact('grupo', 'materiales'));
}
public function create()
{
$turnos= Turno::all();
+120 -35
View File
@@ -2,63 +2,148 @@
namespace App\Http\Controllers;
use App\Models\Alumno;
use App\Models\Docente;
use App\Models\Entrega;
use App\Models\Grupo;
use App\Models\Material;
use App\Models\Tarea;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
class TareaController extends Controller
{
/**
* Display a listing of the resource.
*/
public function index()
public function index(Grupo $grupo, Material $material)
{
//
$tareas = Tarea::where('grupo_id', $grupo->id)
->where('material_id', $material->id)
->withCount([
'entregas',
'entregas as pendientes_count' => fn($q) => $q->where('status', 'pendiente'),
'entregas as calificadas_count' => fn($q) => $q->where('status', 'calificada'),
])
->orderByDesc('created_at')
->get();
return view('tarea.index', compact('grupo', 'material', 'tareas'));
}
/**
* Show the form for creating a new resource.
*/
public function create()
public function create(Grupo $grupo, Material $material)
{
//
return view('tarea.create', compact('grupo', 'material'));
}
/**
* Store a newly created resource in storage.
*/
public function store(Request $request)
public function store(Request $request, Grupo $grupo, Material $material)
{
//
$request->validate([
'titulo' => 'required|string|max:255',
'descripcion' => 'nullable|string',
'fecha_entrega' => 'nullable|date',
]);
$docente = Docente::whereHas('docentes', fn($q) => $q->where('id', Auth::id()))->firstOrFail();
$tarea = Tarea::create([
'titulo' => $request->titulo,
'descripcion' => $request->descripcion,
'fecha_entrega' => $request->fecha_entrega,
'material_id' => $material->id,
'grupo_id' => $grupo->id,
'docente_id' => $docente->id,
]);
foreach ($grupo->alumnos as $alumno) {
Entrega::create([
'tarea_id' => $tarea->id,
'alumno_id' => $alumno->id,
'status' => 'sin_subir',
]);
}
return redirect()->route('grupo.material.tareas', [$grupo, $material])
->with('info', 'Tarea creada correctamente.');
}
/**
* Display the specified resource.
*/
public function show(string $id)
public function show(Grupo $grupo, Material $material, Tarea $tarea)
{
//
$alumnos = $grupo->alumnos()->with('alumnos')->get();
$entregasExistentes = Entrega::where('tarea_id', $tarea->id)->pluck('alumno_id')->toArray();
foreach ($alumnos as $alumno) {
if (!in_array($alumno->id, $entregasExistentes)) {
Entrega::create([
'tarea_id' => $tarea->id,
'alumno_id' => $alumno->id,
'status' => 'sin_subir',
]);
}
}
$entregas = Entrega::where('tarea_id', $tarea->id)
->with('alumno.alumnos')
->get()
->keyBy('alumno_id');
return view('tarea.show', compact('grupo', 'material', 'tarea', 'alumnos', 'entregas'));
}
/**
* Show the form for editing the specified resource.
*/
public function edit(string $id)
public function edit(Grupo $grupo, Material $material, Tarea $tarea)
{
//
return view('tarea.create', compact('grupo', 'material', 'tarea'));
}
/**
* Update the specified resource in storage.
*/
public function update(Request $request, string $id)
public function update(Request $request, Grupo $grupo, Material $material, Tarea $tarea)
{
//
$request->validate([
'titulo' => 'required|string|max:255',
'descripcion' => 'nullable|string',
'fecha_entrega' => 'nullable|date',
]);
$tarea->update([
'titulo' => $request->titulo,
'descripcion' => $request->descripcion,
'fecha_entrega' => $request->fecha_entrega,
]);
return redirect()->route('grupo.material.tareas', [$grupo, $material])
->with('info', 'Tarea actualizada correctamente.');
}
/**
* Remove the specified resource from storage.
*/
public function destroy(string $id)
public function destroy(Grupo $grupo, Material $material, Tarea $tarea)
{
//
// Borrar archivos físicos de cada entrega antes de eliminar los registros
$tarea->entregas()
->whereNotNull('archivo')
->pluck('archivo')
->each(fn($path) => Storage::delete($path));
$tarea->delete(); // las entregas se eliminan en cascada por FK
return redirect()->route('grupo.material.tareas', [$grupo, $material])
->with('info', 'Tarea eliminada.');
}
public function calificar(Request $request, Tarea $tarea, Alumno $alumno)
{
$request->validate([
'calificacion' => 'required|numeric|min:0|max:10',
'comentario' => 'nullable|string',
]);
$entrega = Entrega::where('tarea_id', $tarea->id)
->where('alumno_id', $alumno->id)
->firstOrFail();
$entrega->update([
'calificacion' => $request->calificacion,
'comentario' => $request->comentario,
'status' => 'calificada',
]);
return back()->with('info', 'Calificación guardada.');
}
}
+95 -43
View File
@@ -2,9 +2,12 @@
namespace App\Livewire;
use App\Models\Docente;
use App\Models\Entrega;
use App\Models\Material;
use App\Models\Tarea;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Livewire\Component;
class GrupoDocente extends Component
@@ -16,42 +19,45 @@ class GrupoDocente extends Component
public $materia_id;
public $materiasSelect;
public function mount($grupo)
{
$this->grupo = $grupo; // <- primero asignar el grupo
// Para reemplazo de docente
public $reemplazoDocenteActualId;
public $reemplazoMateriaId;
public $reemplazoDocenteId = '';
public $reemplazoMateriaNombre = '';
$user = Auth::user();
$plantelesIds = $user->plantelUsuarios->pluck('id');
public function mount($grupo)
{
$this->grupo = $grupo;
$this->docentes = Docente::with(['docentes.plantelUsuarios'])
->whereHas('docentes', function($query) use ($plantelesIds) {
$query->whereHas('plantelUsuarios', function($q) use ($plantelesIds) {
$q->whereIn('plantels.id', $plantelesIds);
});
$user = Auth::user();
$plantelesIds = $user->plantelUsuarios->pluck('id');
$this->docentes = Docente::with(['docentes.plantelUsuarios'])
->whereHas('docentes', function ($query) use ($plantelesIds) {
$query->whereHas('plantelUsuarios', function ($q) use ($plantelesIds) {
$q->whereIn('plantels.id', $plantelesIds);
});
})
->get();
$this->materias = Material::whereHas('carreraMaterial', function ($query) use ($grupo) {
$query->where('carreras.id', $grupo->plan->carrera);
})
->orderBy('ciclo')
->get();
// Todas las materias de la carrera sin filtro de ciclo
$this->materias = Material::whereHas('carreraMaterial', function($query) use ($grupo) {
$query->where('carreras.id', $grupo->plan->carrera);
})
->orderBy('ciclo')
->get();
$materiasAsignadas = DB::table('docente_grupo')
->where('grupo_id', $this->grupo->id)
->pluck('materia_id')
->toArray();
$materiasAsignadas = DB::table('docente_grupo')
->where('grupo_id', $this->grupo->id)
->pluck('materia_id')
->toArray();
$this->materiasSelect = Material::whereHas('carreraMaterial', function($query) use ($grupo) {
$query->where('carreras.id', $grupo->plan->carrera);
})
->where('ciclo', $this->grupo->ciclo) // solo ciclo actual
->when(!empty($materiasAsignadas), function($query) use ($materiasAsignadas) {
$query->whereNotIn('id', $materiasAsignadas); // excluir ya asignadas
})
->get();
}
$this->materiasSelect = Material::whereHas('carreraMaterial', function ($query) use ($grupo) {
$query->where('carreras.id', $grupo->plan->carrera);
})
->where('ciclo', $this->grupo->ciclo)
->when(!empty($materiasAsignadas), fn($q) => $q->whereNotIn('id', $materiasAsignadas))
->get();
}
public function guardar()
{
@@ -61,33 +67,79 @@ $this->materiasSelect = Material::whereHas('carreraMaterial', function($query) u
]);
$this->grupo->docentes()->attach($this->docente_id, [
'materia_id' => $this->materia_id
'materia_id' => $this->materia_id,
]);
$this->reset(['docente_id', 'materia_id']);
$this->dispatch('close-modal-docente');
$this->dispatch('swal',
icon: 'success',
title: '¡Listo!',
icon: 'success', title: '¡Listo!',
text: 'Docente asignado correctamente',
timer: 3000,
confirm: false,
closeModal: true,
timer: 3000, confirm: false, closeModal: true,
);
}
public function eliminar($docenteId, $materiaId)
{
// Borrar archivos físicos de las entregas antes de eliminar en cascada
$tareaIds = Tarea::where('grupo_id', $this->grupo->id)
->where('material_id', $materiaId)
->where('docente_id', $docenteId)
->pluck('id');
Entrega::whereIn('tarea_id', $tareaIds)
->whereNotNull('archivo')
->pluck('archivo')
->each(fn($path) => Storage::delete($path));
Tarea::whereIn('id', $tareaIds)->delete();
$this->grupo->docentes()->wherePivot('materia_id', $materiaId)->detach($docenteId);
$this->dispatch('swal',
icon: 'success',
title: 'Eliminado',
text: 'Docente removido de la materia correctamente',
timer: 2000,
confirm: false,
closeModal: true,
icon: 'success', title: 'Eliminado',
text: 'Docente y sus tareas removidos correctamente',
timer: 2000, confirm: false, closeModal: true,
);
}
public function abrirReemplazo($docenteId, $materiaId, $materiaNombre)
{
$this->reemplazoDocenteActualId = $docenteId;
$this->reemplazoMateriaId = $materiaId;
$this->reemplazoMateriaNombre = $materiaNombre;
$this->reemplazoDocenteId = '';
$this->dispatch('open-modal-reemplazo');
}
public function reemplazar()
{
$this->validate([
'reemplazoDocenteId' => 'required|exists:docentes,id|different:reemplazoDocenteActualId',
], [
'reemplazoDocenteId.required' => 'Selecciona un docente.',
'reemplazoDocenteId.different' => 'Debes elegir un docente diferente al actual.',
]);
// Actualizar el registro en docente_grupo
DB::table('docente_grupo')
->where('grupo_id', $this->grupo->id)
->where('materia_id', $this->reemplazoMateriaId)
->where('docente_id', $this->reemplazoDocenteActualId)
->update(['docente_id' => $this->reemplazoDocenteId]);
// Reasignar las tareas al nuevo docente (conserva entregas intactas)
Tarea::where('grupo_id', $this->grupo->id)
->where('material_id', $this->reemplazoMateriaId)
->where('docente_id', $this->reemplazoDocenteActualId)
->update(['docente_id' => $this->reemplazoDocenteId]);
$this->reset(['reemplazoDocenteId', 'reemplazoMateriaId', 'reemplazoDocenteActualId', 'reemplazoMateriaNombre']);
$this->dispatch('close-modal-reemplazo');
$this->dispatch('swal',
icon: 'success', title: '¡Docente reemplazado!',
text: 'El nuevo docente fue asignado y las tareas previas se conservaron.',
timer: 3500, confirm: false, closeModal: true,
);
}
+73
View File
@@ -0,0 +1,73 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Anuncio extends Model
{
protected $fillable = [
'user_id', 'title', 'body', 'image_path',
'type', 'audience', 'expires_at', 'active',
];
protected $casts = [
'audience' => 'array',
'expires_at' => 'datetime',
'active' => 'boolean',
];
// Configuración visual por tipo
public const TYPES = [
'info' => ['label' => 'Información', 'color' => 'primary', 'icon' => 'fa-info-circle'],
'promocion' => ['label' => 'Promoción', 'color' => 'success', 'icon' => 'fa-tag'],
'mantenimiento' => ['label' => 'Mantenimiento', 'color' => 'warning', 'icon' => 'fa-tools'],
'aviso' => ['label' => 'Aviso', 'color' => 'danger', 'icon' => 'fa-exclamation-triangle'],
];
public const ROLES = [
'all' => 'Todos',
'Alumno' => 'Alumnos',
'Docente' => 'Docentes',
'Admin' => 'Administradores',
'Control escolar' => 'Control escolar',
'Coordinación académica' => 'Coordinación académica',
'Programador' => 'Programadores',
'Promotor' => 'Promotores',
];
public function author(): BelongsTo
{
return $this->belongsTo(User::class, 'user_id');
}
// Solo activos y no vencidos
public function scopeVigentes(Builder $q): void
{
$q->where('active', true)
->where(fn($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()));
}
// Filtra por roles del usuario
public function scopeParaRoles(Builder $q, array $roles): void
{
$q->where(function ($q) use ($roles) {
$q->whereJsonContains('audience', 'all');
foreach ($roles as $role) {
$q->orWhereJsonContains('audience', $role);
}
});
}
public function typeConfig(): array
{
return self::TYPES[$this->type] ?? self::TYPES['info'];
}
public function isExpired(): bool
{
return $this->expires_at && $this->expires_at->isPast();
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ class Docente extends Model
// Relación con Material
public function materias(): BelongsToMany
{
return $this->belongsToMany(Material::class, 'docente_grupo')
return $this->belongsToMany(Material::class, 'docente_grupo', 'docente_id', 'materia_id')
->withPivot('grupo_id');
}
}
+13
View File
@@ -0,0 +1,13 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Entrega extends Model
{
protected $guarded = [];
public function tarea() { return $this->belongsTo(Tarea::class); }
public function alumno() { return $this->belongsTo(Alumno::class); }
}
+4 -8
View File
@@ -3,17 +3,13 @@
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
class Tarea extends Model
{
protected $guarded = [];
public $timestamps = false;
// Relación con Carrera
public function carreraMaterial(): BelongsToMany
{
return $this->belongsToMany(Carrera::class);
}
public function material() { return $this->belongsTo(Material::class); }
public function grupo() { return $this->belongsTo(Grupo::class); }
public function docente() { return $this->belongsTo(Docente::class); }
public function entregas() { return $this->hasMany(Entrega::class); }
}