Files
lash_vanshy/app/Http/Controllers/Admin/CitaController.php

346 lines
11 KiB
PHP
Executable File

<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Http\Requests\CitaRequest;
use App\Models\Cita;
use App\Models\HorarioBloqueado;
use App\Models\Mensaje;
use Carbon\Carbon;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
class CitaController extends Controller
{
/**
* Mostrar lista de citas
*/
public function index(Request $request): View
{
$query = Cita::with('mensaje')->orderBy('fecha', 'desc')->orderBy('hora_inicio', 'desc');
// Filtros
if ($request->filled('estado')) {
$query->where('estado', $request->estado);
}
if ($request->filled('fecha')) {
$query->whereDate('fecha', $request->fecha);
}
if ($request->filled('fecha_inicio') && $request->filled('fecha_fin')) {
$query->whereBetween('fecha', [$request->fecha_inicio, $request->fecha_fin]);
}
if ($request->filled('buscar')) {
$buscar = $request->buscar;
$query->where(function ($q) use ($buscar) {
$q->where('nombre_cliente', 'like', "%{$buscar}%")
->orWhere('email_cliente', 'like', "%{$buscar}%")
->orWhere('telefono_cliente', 'like', "%{$buscar}%")
->orWhere('servicio', 'like', "%{$buscar}%");
});
}
$citas = $query->paginate(15)->appends($request->query());
return view('admin.citas.index', compact('citas'));
}
/**
* Mostrar formulario de creacion
*/
public function create(): View
{
$mensajes = Mensaje::orderBy('created_at', 'desc')->get();
$mensaje = null;
return view('admin.citas.create', compact('mensajes', 'mensaje'));
}
/**
* Mostrar formulario de creacion desde mensaje
*/
public function createFromMensaje(Mensaje $mensaje): View
{
$mensajes = Mensaje::orderBy('created_at', 'desc')->get();
return view('admin.citas.create', compact('mensajes', 'mensaje'));
}
/**
* Guardar nueva cita
*/
public function store(CitaRequest $request): RedirectResponse
{
try {
$data = $request->validated();
Log::info('Datos validados', $data);
// Calcular hora_fin basada en hora_inicio y duracion
$horaInicio = Carbon::parse($data['hora_inicio']);
$horaFin = $horaInicio->addMinutes((int) $data['duracion']);
$data['hora_fin'] = $horaFin->format('H:i:s');
// Verificar disponibilidad antes de crear
$conflicto = Cita::whereDate('fecha', $data['fecha'])
->where(function ($query) use ($data) {
$query->where(function ($q) use ($data) {
$q->whereTime('hora_inicio', '<', $data['hora_fin'])
->whereTime('hora_fin', '>', $data['hora_inicio']);
});
})
->whereIn('estado', ['pendiente', 'confirmada'])
->exists();
if ($conflicto) {
return back()->withErrors(['hora_inicio' => 'Ya existe una cita programada en este horario.'])->withInput();
}
// Verificar si el horario está bloqueado
if (HorarioBloqueado::estaBloqueado($data['fecha'], $data['hora_inicio'])) {
return back()->withErrors(['hora_inicio' => 'El horario está bloqueado.'])->withInput();
}
$cita = Cita::create($data);
Log::info('Cita creada', ['id' => $cita->id]);
return redirect()->route('admin.citas.index')->with('success', 'Cita creada correctamente.');
} catch (\Exception $e) {
Log::error('Error al crear cita: '.$e->getMessage());
return back()->withErrors(['error' => 'Error al crear la cita: '.$e->getMessage()])->withInput();
}
}
/**
* Mostrar detalles de una cita
*/
public function show(Cita $cita): View
{
$cita->load('mensaje');
return view('admin.citas.show', compact('cita'));
}
/**
* Mostrar formulario de edición
*/
public function edit(Cita $cita): View
{
$mensajes = Mensaje::orderBy('created_at', 'desc')->get();
return view('admin.citas.edit', compact('cita', 'mensajes'));
}
/**
* Actualizar cita
*/
public function update(CitaRequest $request, Cita $cita): RedirectResponse
{
$data = $request->validated();
// Calcular hora_fin si cambió hora_inicio o duracion
if ($request->has('hora_inicio') || $request->has('duracion')) {
$horaInicio = Carbon::parse($data['hora_inicio'] ?? $cita->hora_inicio);
$duracion = $data['duracion'] ?? $cita->duracion;
$horaFin = $horaInicio->addMinutes((int) $duracion);
$data['hora_fin'] = $horaFin->format('H:i:s');
}
// Verificar conflicto si se cambia fecha u hora
$conflicto = Cita::where('id', '!=', $cita->id)
->whereDate('fecha', $data['fecha'])
->where(function ($query) use ($data) {
$query->where(function ($q) use ($data) {
$q->whereTime('hora_inicio', '<', $data['hora_fin'])
->whereTime('hora_fin', '>', $data['hora_inicio']);
});
})
->whereIn('estado', ['pendiente', 'confirmada'])
->exists();
if ($conflicto) {
return back()->withErrors(['hora_inicio' => 'Ya existe una cita programada en este horario.'])->withInput();
}
$cita->update($data);
return redirect()->route('admin.citas.index')->with('success', 'Cita actualizada correctamente.');
}
/**
* Eliminar cita
*/
public function destroy(Cita $cita): RedirectResponse
{
$cita->delete();
return redirect()->route('admin.citas.index')->with('success', 'Cita eliminada correctamente.');
}
/**
* Cambiar estado de la cita
*/
public function cambiarEstado(Request $request, Cita $cita): RedirectResponse
{
$request->validate([
'estado' => ['required', 'in:pendiente,confirmada,completada,cancelada'],
]);
$cita->update(['estado' => $request->estado]);
return back()->with('success', 'Estado de la cita actualizado correctamente.');
}
/**
* Mostrar calendario de citas
*/
public function calendario(Request $request): View
{
$fecha = $request->filled('fecha') ? Carbon::parse($request->fecha) : Carbon::now();
$citas = Cita::with('mensaje')
->whereDate('fecha', $fecha)
->whereIn('estado', ['pendiente', 'confirmada'])
->orderBy('hora_inicio')
->get();
return view('admin.citas.calendario', compact('citas', 'fecha'));
}
/**
* Obtener citas para una fecha específica (API)
*/
public function porFecha(Request $request): JsonResponse
{
$request->validate([
'fecha' => ['required', 'date'],
]);
$citas = Cita::whereDate('fecha', $request->fecha)
->whereIn('estado', ['pendiente', 'confirmada'])
->orderBy('hora_inicio')
->get();
return response()->json($citas);
}
/**
* Obtener horarios disponibles para una fecha (API)
*/
public function getHorariosDisponibles(Request $request): JsonResponse
{
$request->validate([
'fecha' => ['required', 'date'],
'duracion' => ['nullable', 'integer', 'min:15', 'max:240'],
]);
$fecha = $request->fecha;
$duracion = $request->duracion ?? 60;
// Verificar si es domingo (no laborable)
$diaSemana = Carbon::parse($fecha)->dayOfWeek;
if ($diaSemana === 0) {
return response()->json([
'error' => 'No se atienden los domingos',
'horarios' => [],
]);
}
// Horario de atención: 9:00 a 19:00 (último turno a las 18:00)
$horaApertura = 9;
$horaCierre = 18;
// Obtener citas existentes para esa fecha
$citasExistentes = Cita::whereDate('fecha', $fecha)
->whereIn('estado', ['pendiente', 'confirmada'])
->orderBy('hora_inicio')
->get();
// Obtener horarios bloqueados
$bloqueos = HorarioBloqueado::whereDate('fecha', $fecha)
->where('activo', true)
->orderBy('hora_inicio')
->get();
// Generar todos los horarios disponibles
$horarios = [];
$intervalo = 30; // intervals of 30 minutes
for ($hora = $horaApertura; $hora < $horaCierre; $hora++) {
for ($minuto = 0; $minuto < 60; $minuto += $intervalo) {
// Check if appointment fits in the schedule
$horaInicioMinutos = $hora * 60 + $minuto;
$horaFinMinutos = $horaInicioMinutos + $duracion;
if ($horaFinMinutos > $horaCierre * 60) {
continue;
}
$horaInicio = sprintf('%02d:%02d', $hora, $minuto);
$horaFinMinutos = $horaInicioMinutos + $duracion;
$horaFinHora = intdiv($horaFinMinutos, 60);
$horaFinMinuto = $horaFinMinutos % 60;
$horaFin = sprintf('%02d:%02d', $horaFinHora, $horaFinMinuto);
// Verificar si hay conflicto con citas existentes
$conflicto = $citasExistentes->contains(function ($cita) use ($horaInicio, $horaFin) {
return $cita->hora_inicio < $horaFin && $cita->hora_fin > $horaInicio;
});
// Verificar si hay conflicto con bloqueos
$bloqueado = $bloqueos->contains(function ($bloqueo) use ($horaInicio, $horaFin) {
return $bloqueo->hora_inicio < $horaFin && $bloqueo->hora_fin > $horaInicio;
});
if (! $conflicto && ! $bloqueado) {
$horarios[] = [
'hora' => $horaInicio,
'hora_fin' => $horaFin,
];
}
}
}
return response()->json([
'fecha' => $fecha,
'duracion' => $duracion,
'horarios' => $horarios,
]);
}
/**
* Obtener citas en un rango de fechas (API para calendario)
*/
public function getCitasPorFecha(Request $request): JsonResponse
{
$request->validate([
'inicio' => ['required', 'date'],
'fin' => ['required', 'date'],
]);
$citas = Cita::with('mensaje')
->whereBetween('fecha', [$request->inicio, $request->fin])
->whereIn('estado', ['pendiente', 'confirmada'])
->orderBy('fecha')
->orderBy('hora_inicio')
->get();
$bloqueos = HorarioBloqueado::whereBetween('fecha', [$request->inicio, $request->fin])
->where('activo', true)
->orderBy('fecha')
->orderBy('hora_inicio')
->get();
return response()->json([
'citas' => $citas,
'bloqueos' => $bloqueos,
]);
}
}