<?php

use App\Http\Controllers\AuthController;
use App\Models\ContenidoWeb;
use App\Models\Baja;
use App\Models\Agenda;
use App\Models\Dia;
use App\Models\DiaDeAtencion;
use App\Models\DiaPreferencia;
use App\Models\EstadoTurno;
use App\Models\Feriado;
use App\Models\Formulario;
use App\Models\HorarioDeAtencion;
use App\Models\HorarioPreferencia;
use App\Models\HorarioReceso;
use App\Models\Modalidad;
use App\Models\ModoVacaciones;
use App\Models\CredencialProfesional;
use App\Models\Foto;
use App\Models\AccionLog;
use App\Models\Administrador;
use App\Models\CredencialCliente;
use App\Models\LogSeguridad;
use App\Models\Cliente;
use App\Models\DocumentacionCliente;
use App\Models\Bug;
use App\Models\Error;
use App\Models\FotoBug;
use App\Models\Persona;
use App\Models\Profesion;
use App\Models\Profesional;
use App\Models\Turno;
use App\Models\Servicio;
use App\Models\Telefono;
use App\Models\FaqAsistente;
use App\Models\AsistenteSinRespuesta;
use App\Models\Notificacion;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
use Illuminate\Validation\Rule;
use Illuminate\Database\QueryException;


if (!defined('FAQ_INTENCION_BURBUJA')) {
    define('FAQ_INTENCION_BURBUJA', 'ui_burbuja');
}
if (!defined('FAQ_INTENCION_PANEL_INICIO')) {
    define('FAQ_INTENCION_PANEL_INICIO', 'ui_panel_inicio');
}
if (!defined('FAQ_INTENCION_ERROR')) {
    define('FAQ_INTENCION_ERROR', 'ui_error');
}
if (!defined('FAQ_INTENCION_NOMBRE')) {
    define('FAQ_INTENCION_NOMBRE', 'ui_nombre');
}
if (!defined('FAQ_INTENCION_CHIPS')) {
    define('FAQ_INTENCION_CHIPS', 'ui_chips');
}

$obtenerNombrePersonaLog = function (?int $personaResponsableId): ?string {
    if (!$personaResponsableId) {
        return null;
    }

    $persona = Persona::query()->find($personaResponsableId);

    if (!$persona) {
        return null;
    }

    $nombreCompleto = trim((string) (($persona->nombre ?? '') . ' ' . ($persona->apellido ?? '')));

    return $nombreCompleto !== '' ? $nombreCompleto : null;
};

$registrarLogSeguridad = function (Request $request, string $accionDescripcion, string $descripcion) use ($obtenerNombrePersonaLog): void {
    $accion = AccionLog::firstWhere('descripcion', $accionDescripcion);

    if (!$accion) {
        return;
    }

    $credencialIdSesion = (int) $request->session()->get('personal_credencial_id', 0);
    $personaResponsableId = Administrador::query()
        ->where('credencialprofesional_id', $credencialIdSesion)
        ->value('persona_id');

    if (!$personaResponsableId) {
        return;
    }

    LogSeguridad::create([
        'descripcion' => $descripcion,
        'fechahora' => now(),
        'IPorigen' => (string) $request->ip(),
        'rol' => 'ADMIN',
        'persona_id' => (int) $personaResponsableId,
        'accion_id' => $accion->id,
        'accion_descripcion' => (string) ($accion->descripcion ?? $accionDescripcion),
        'responsable_nombre' => $obtenerNombrePersonaLog((int) $personaResponsableId),
    ]);
};

$registrarLogSeguridadProfesional = function (Request $request, string $accionDescripcion, string $descripcion) use ($obtenerNombrePersonaLog): void {
    $accion = AccionLog::firstWhere('descripcion', $accionDescripcion);

    if (!$accion) {
        return;
    }

    $credencialIdSesion = (int) $request->session()->get('personal_credencial_id', 0);
    $personaResponsableId = Profesional::query()
        ->where('credencialprofesional_id', $credencialIdSesion)
        ->value('persona_id');

    if (!$personaResponsableId) {
        return;
    }

    LogSeguridad::create([
        'descripcion' => $descripcion,
        'fechahora' => now(),
        'IPorigen' => (string) $request->ip(),
        'rol' => 'PROFESIONAL',
        'persona_id' => (int) $personaResponsableId,
        'accion_id' => $accion->id,
        'accion_descripcion' => (string) ($accion->descripcion ?? $accionDescripcion),
        'responsable_nombre' => $obtenerNombrePersonaLog((int) $personaResponsableId),
    ]);
};

$registrarLogSeguridadDirecto = function (?int $personaResponsableId, string $rol, string $ipOrigen, string $accionDescripcion, string $descripcion) use ($obtenerNombrePersonaLog): void {
    if (!$personaResponsableId) {
        return;
    }

    $accion = AccionLog::firstWhere('descripcion', $accionDescripcion);

    if (!$accion) {
        return;
    }

    LogSeguridad::create([
        'descripcion' => $descripcion,
        'fechahora' => now(),
        'IPorigen' => $ipOrigen,
        'rol' => $rol,
        'persona_id' => (int) $personaResponsableId,
        'accion_id' => $accion->id,
        'accion_descripcion' => (string) ($accion->descripcion ?? $accionDescripcion),
        'responsable_nombre' => $obtenerNombrePersonaLog((int) $personaResponsableId),
    ]);
};

$validarSesionAdmin = function (Request $request) {
    if (!$request->session()->get('personal_auth') || $request->session()->get('personal_rol') !== 'ADMIN') {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como administradora.');
    }

    return null;
};

$honeypotValido = function (Request $request): bool {
    return trim((string) $request->input('website', '')) === '';
};

$hashearTokenRecuperacion = function (?string $token): string {
    return hash('sha256', trim((string) ($token ?? '')));
};

$buscarCredencialClientePorResetToken = function (string $token) use ($hashearTokenRecuperacion): ?CredencialCliente {
    $tokenPlano = trim((string) $token);

    if ($tokenPlano === '') {
        return null;
    }

    $tokenHasheado = $hashearTokenRecuperacion($tokenPlano);

    return CredencialCliente::query()
        ->where(function ($query) use ($tokenPlano, $tokenHasheado): void {
            $query->where('reset_token', $tokenHasheado)
                ->orWhere('reset_token', $tokenPlano);
        })
        ->where('reset_expira_en', '>=', now())
        ->first();
};

$buscarCredencialProfesionalPorResetToken = function (string $token, ?string $rol = null) use ($hashearTokenRecuperacion): ?CredencialProfesional {
    $tokenPlano = trim((string) $token);

    if ($tokenPlano === '') {
        return null;
    }

    $tokenHasheado = $hashearTokenRecuperacion($tokenPlano);

    $query = CredencialProfesional::query()
        ->where(function ($subQuery) use ($tokenPlano, $tokenHasheado): void {
            $subQuery->where('reset_token', $tokenHasheado)
                ->orWhere('reset_token', $tokenPlano);
        })
        ->where('reset_expira_en', '>=', now());

    if ($rol !== null && trim($rol) !== '') {
        $query->where('rol', strtoupper(trim($rol)));
    }

    return $query->first();
};

$normalizarCelular = function (?string $celular): string {
    return preg_replace('/\D+/', '', (string) ($celular ?? '')) ?? '';
};

$normalizarNombreArchivoPrivado = function (string $nombreArchivo): string {
    return basename(str_replace(['\\', '/'], DIRECTORY_SEPARATOR, trim($nombreArchivo)));
};

$directorioDocumentacionPrivada = function (int $clienteId): string {
    return storage_path('app/private/documentacion-clientes/cliente_' . $clienteId);
};

$directorioDocumentacionPublicaLegacy = function (int $clienteId): string {
    return public_path('uploads/documentacion-clientes/cliente_' . $clienteId);
};

$resolverRutaDocumentacionCliente = function (int $clienteId, string $nombreArchivo, bool $migrarLegacy = true) use ($normalizarNombreArchivoPrivado, $directorioDocumentacionPrivada, $directorioDocumentacionPublicaLegacy): ?string {
    $nombreSeguro = $normalizarNombreArchivoPrivado($nombreArchivo);

    if ($nombreSeguro === '') {
        return null;
    }

    $rutaPrivada = $directorioDocumentacionPrivada($clienteId) . DIRECTORY_SEPARATOR . $nombreSeguro;
    if (File::exists($rutaPrivada)) {
        return $rutaPrivada;
    }

    $rutaPublicaLegacy = $directorioDocumentacionPublicaLegacy($clienteId) . DIRECTORY_SEPARATOR . $nombreSeguro;
    if (!File::exists($rutaPublicaLegacy)) {
        return null;
    }

    if (!$migrarLegacy) {
        return $rutaPublicaLegacy;
    }

    if (!File::isDirectory($directorioDocumentacionPrivada($clienteId))) {
        File::makeDirectory($directorioDocumentacionPrivada($clienteId), 0755, true);
    }

    try {
        File::move($rutaPublicaLegacy, $rutaPrivada);
        return $rutaPrivada;
    } catch (\Throwable $e) {
        logger()->warning('No se pudo mover documentación sensible a almacenamiento privado.', [
            'cliente_id' => $clienteId,
            'archivo' => $nombreSeguro,
            'error' => $e->getMessage(),
        ]);

        return $rutaPublicaLegacy;
    }
};

$eliminarDocumentacionCliente = function (int $clienteId, string $nombreArchivo) use ($normalizarNombreArchivoPrivado, $directorioDocumentacionPrivada, $directorioDocumentacionPublicaLegacy): void {
    $nombreSeguro = $normalizarNombreArchivoPrivado($nombreArchivo);

    if ($nombreSeguro === '') {
        return;
    }

    foreach ([
        $directorioDocumentacionPrivada($clienteId) . DIRECTORY_SEPARATOR . $nombreSeguro,
        $directorioDocumentacionPublicaLegacy($clienteId) . DIRECTORY_SEPARATOR . $nombreSeguro,
    ] as $rutaArchivo) {
        if (File::exists($rutaArchivo)) {
            File::delete($rutaArchivo);
        }
    }
};

$obtenerProfesionalesActivosCompatibles = function (int $profesionId) {
    return Profesional::query()
        ->where('baja_id', 1)
        ->where(function ($query) use ($profesionId): void {
            $query->where('profesion_id', $profesionId)
                ->orWhereHas('profesiones', fn ($q) => $q->where('profesiones.id', $profesionId));
        })
        ->pluck('id');
};

$resolverProfesionalCompatible = function (int $profesionalId, int $profesionId): ?Profesional {
    $profesionalBase = Profesional::query()->find($profesionalId);

    if (!$profesionalBase) {
        return null;
    }

    return Profesional::query()
        ->where('baja_id', 1)
        ->where(function ($query) use ($profesionalBase, $profesionalId): void {
            $query->where('id', $profesionalId);

            if ((int) ($profesionalBase->persona_id ?? 0) > 0) {
                $query->orWhere('persona_id', (int) $profesionalBase->persona_id);
            }

            if ((int) ($profesionalBase->credencialprofesional_id ?? 0) > 0) {
                $query->orWhere('credencialprofesional_id', (int) $profesionalBase->credencialprofesional_id);
            }
        })
        ->where(function ($query) use ($profesionId): void {
            $query->where('profesion_id', $profesionId)
                ->orWhereHas('profesiones', fn ($q) => $q->where('profesiones.id', $profesionId));
        })
        ->orderByRaw('CASE WHEN id = ? THEN 0 ELSE 1 END', [$profesionalId])
        ->first();
};

$formularioFueAceptadoPorOtroProfesional = function (int $formularioId, int $profesionalId): bool {
    $estadoGeneralFormulario = mb_strtolower(trim((string) Formulario::query()
        ->where('id', $formularioId)
        ->value('estado')));

    if ($estadoGeneralFormulario !== 'aceptado') {
        return false;
    }

    return DB::table('profesionales_formularios')
        ->where('formulario_id', $formularioId)
        ->where('profesional_id', '!=', $profesionalId)
        ->whereRaw("LOWER(TRIM(estado)) = 'aceptado'")
        ->exists();
};

$recalcularEstadoGeneralFormulario = function (Formulario $formulario): string {
    $profesionFormularioId = (int) $formulario->profesion_id;

    $queryBase = DB::table('profesionales_formularios as pf')
        ->join('profesionales as p', 'p.id', '=', 'pf.profesional_id')
        ->where('pf.formulario_id', (int) $formulario->id)
        ->where('p.baja_id', 1)
        ->where(function ($query) use ($profesionFormularioId): void {
            $query->where('p.profesion_id', $profesionFormularioId)
                ->orWhereExists(function ($subquery) use ($profesionFormularioId): void {
                    $subquery->select(DB::raw(1))
                        ->from('profesionales_profesiones as pp')
                        ->whereColumn('pp.profesional_id', 'p.id')
                        ->where('pp.profesion_id', $profesionFormularioId);
                });
        });

    $hayAceptado = (clone $queryBase)
        ->whereRaw("LOWER(TRIM(pf.estado)) = 'aceptado'")
        ->exists();

    if ($hayAceptado) {
        $nuevoEstado = 'Aceptado';
    } else {
        $asignadosActivos = (clone $queryBase)->count();

        $hayActivosSinRechazar = (clone $queryBase)
            ->where(function ($query): void {
                $query->whereNull('pf.estado')
                    ->orWhereRaw("LOWER(TRIM(pf.estado)) <> 'rechazado'");
            })
            ->exists();

        $nuevoEstado = ($asignadosActivos > 0 && !$hayActivosSinRechazar)
            ? 'Rechazado por todos'
            : 'Pendiente';
    }

    if (trim((string) $formulario->estado) !== $nuevoEstado) {
        $formulario->update([
            'estado' => $nuevoEstado,
        ]);
    }

    return $nuevoEstado;
};

$enviarCorreoTurno = function (
    string $tipo,
    string $correoDestino,
    string $nombreDestino,
    \Illuminate\Support\Carbon $inicio,
    string $servicio,
    string $modalidad,
    string $nombreProfesional
): void {
    $correo = trim($correoDestino);
    if ($correo === '') {
        return;
    }

    $nombreCliente = trim($nombreDestino) !== '' ? trim($nombreDestino) : 'paciente';
    $fecha = $inicio->format('d/m/Y');
    $hora = $inicio->format('H:i');
    $servicioTxt = trim($servicio) !== '' ? trim($servicio) : 'Sin servicio';
    $modalidadTxt = trim($modalidad) !== '' ? trim($modalidad) : 'Sin modalidad';
    $profesionalTxt = trim($nombreProfesional) !== '' ? trim($nombreProfesional) : 'profesional asignado';

    $esCancelacion = $tipo === 'cancelacion';
    $esReprogramacion = $tipo === 'reprogramacion';
    $tipoNotificacion = $esCancelacion
        ? 'turno_cancelado'
        : ($esReprogramacion ? 'turno_reprogramado' : 'turno_otorgado');

    $plantillasPorTipo = [
        'turno_otorgado' => [
            'mensaje_inicio' => 'Se asigno tu turno con los siguientes datos:',
            'mensaje_final' => 'Si necesitas reprogramar o cancelar, por favor comunicate por WhatsApp al 343-4632444',
        ],
        'turno_cancelado' => [
            'mensaje_inicio' => 'Te informamos que tu turno fue cancelado con los siguientes datos:',
            'mensaje_final' => 'Si necesitas coordinar un nuevo turno, por favor comunicate por WhatsApp al 343-4632444',
        ],
        'turno_reprogramado' => [
            'mensaje_inicio' => 'Te informamos que tu turno fue reprogramado con los siguientes datos:',
            'mensaje_final' => 'Si tenes dudas sobre este cambio, por favor comunicate por WhatsApp al 343-4632444',
        ],
    ];

    $plantilla = Notificacion::query()->firstOrCreate(
        ['tipo' => $tipoNotificacion],
        $plantillasPorTipo[$tipoNotificacion]
    );

    $mensajeInicio = trim((string) ($plantilla->mensaje_inicio ?? ''));
    $mensajeFinal = trim((string) ($plantilla->mensaje_final ?? ''));

    $asunto = $esCancelacion
        ? 'Cancelacion de turno'
        : ($esReprogramacion ? 'Reprogramacion de turno' : 'Confirmacion de turno asignado');
    $intro = $mensajeInicio !== ''
        ? $mensajeInicio
        : ($plantillasPorTipo[$tipoNotificacion]['mensaje_inicio'] ?? '');

    $ubicacion = "Dr. Luis Pasteur 141, Paraná, Entre Ríos, Argentina";
    $cuerpo = "Hola {$nombreCliente},\n\n"
        . $intro . "\n"
        . "- Fecha: {$fecha}\n"
        . "- Hora: {$hora}\n"
        . "- Servicio: {$servicioTxt}\n"
        . "- Modalidad: {$modalidadTxt}\n"
        . "- Profesional: {$profesionalTxt}\n"
        . "- Ubicación: {$ubicacion}\n\n"
        . ($mensajeFinal !== ''
            ? $mensajeFinal
            : ($plantillasPorTipo[$tipoNotificacion]['mensaje_final'] ?? ''))
        . "\n";

    try {
        Mail::raw($cuerpo, function ($message) use ($correo, $nombreCliente, $asunto): void {
            $message->to($correo, $nombreCliente)->subject($asunto);
        });
    } catch (\Throwable $e) {
        logger()->warning('No se pudo enviar correo de turno.', [
            'tipo' => $tipo,
            'correo' => $correo,
            'error' => $e->getMessage(),
        ]);
    }
};

// Devuelve advertencias de agenda/configuración. Si la agenda aún no tiene días de atención cargados,
// permite la asignación manual sin advertir por falta de configuración horaria.
$obtenerAdvertenciasTurno = function (int $agendaId, int $duracionMinutos, \Illuminate\Support\Carbon $inicio): array {
    $fechaStr = $inicio->format('Y-m-d');
    $finTurno = $inicio->copy()->addMinutes($duracionMinutos);
    $advertencias = [];

    $esFeriado = Feriado::query()
        ->where('agenda_id', $agendaId)
        ->where('fecha', $fechaStr)
        ->exists();

    if ($esFeriado) {
        $advertencias[] = 'El día elegido es feriado.';
    }

    $enVacaciones = ModoVacaciones::query()
        ->where('agenda_id', $agendaId)
        ->where('inicio', '<=', $fechaStr)
        ->where('fin', '>=', $fechaStr)
        ->exists();

    if ($enVacaciones) {
        $advertencias[] = 'El profesional se encuentra en período de vacaciones en esa fecha.';
    }

    $diaDescPorNumero = [
        0 => ['domingo'],
        1 => ['lunes'],
        2 => ['martes'],
        3 => ['miercoles', 'miércoles'],
        4 => ['jueves'],
        5 => ['viernes'],
        6 => ['sabado', 'sábado'],
    ];

    $diasPos = $diaDescPorNumero[(int) $inicio->dayOfWeek] ?? [];
    $tieneDiasConfigurados = DiaDeAtencion::query()
        ->where('agenda_id', $agendaId)
        ->exists();

    $diaAtencion = DiaDeAtencion::query()
        ->with(['horariosAtenciones', 'horariosRecesos'])
        ->where('agenda_id', $agendaId)
        ->whereHas('dias', function ($q) use ($diasPos) {
            $q->whereRaw(
                'LOWER(TRIM(descripcion)) IN (' . implode(',', array_fill(0, count($diasPos), '?')) . ')',
                $diasPos
            );
        })
        ->first();

    if (!$diaAtencion) {
        if ($tieneDiasConfigurados) {
            $advertencias[] = 'El día elegido no forma parte de los días laborales configurados.';
        }

        return $advertencias;
    }

    $inicioStr = $inicio->format('H:i:s');
    $finStr    = $finTurno->format('H:i:s');

    if ($diaAtencion->horariosAtenciones->isNotEmpty()) {
        $dentroDeHorario = $diaAtencion->horariosAtenciones->contains(function ($horario) use ($inicioStr, $finStr) {
            $hi = substr((string) $horario->horariocomienzo, 0, 8);
            $hf = substr((string) $horario->horariofin, 0, 8);

            return $inicioStr >= $hi && $finStr <= $hf;
        });

        if (!$dentroDeHorario) {
            $advertencias[] = 'El horario elegido está fuera del horario de atención configurado.';
        }
    }

    $enReceso = $diaAtencion->horariosRecesos->contains(function ($receso) use ($inicioStr, $finStr) {
        $ri = substr((string) $receso->comienzo, 0, 8);
        $rf = substr((string) $receso->fin, 0, 8);

        return $inicioStr < $rf && $finStr > $ri;
    });

    if ($enReceso) {
        $advertencias[] = 'El horario elegido cae dentro de un receso.';
    }

    return $advertencias;
};

$verificarConflictoTurno = function (int $agendaId, int $duracionMinutos, \Illuminate\Support\Carbon $inicio, ?int $turnoIgnoradoId = null): ?string {
    $finTurno = $inicio->copy()->addMinutes($duracionMinutos);

    $estadoCanceladoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])
        ->value('id') ?? 2);

    $hayConflicto = Turno::query()
        ->where('agenda_id', $agendaId)
        ->where('estadoturno_id', '!=', $estadoCanceladoId)
        ->when($turnoIgnoradoId !== null, function ($query) use ($turnoIgnoradoId) {
            $query->where('id', '!=', $turnoIgnoradoId);
        })
        ->where('inicio', '<', $finTurno)
        ->whereRaw('DATE_ADD(inicio, INTERVAL ? MINUTE) > ?', [$duracionMinutos, $inicio])
        ->exists();

    if ($hayConflicto) {
        return 'Ya existe un turno en ese horario.';
    }

    return null;
};

// Retorna null si el horario está disponible, o un string con el motivo si no lo está.
$verificarDisponibilidadTurno = function (int $agendaId, int $duracionMinutos, \Illuminate\Support\Carbon $inicio, ?int $turnoIgnoradoId = null) use ($obtenerAdvertenciasTurno, $verificarConflictoTurno): ?string {
    $advertencias = $obtenerAdvertenciasTurno($agendaId, $duracionMinutos, $inicio);
    if (!empty($advertencias)) {
        return implode(' ', $advertencias);
    }

    return $verificarConflictoTurno($agendaId, $duracionMinutos, $inicio, $turnoIgnoradoId);
};

// Busca el primer turno disponible desde el dia siguiente, respetando agenda y preferencias del formulario.
$buscarTurnoAutomatico = function (Formulario $formulario, int $profesionalId) use ($verificarDisponibilidadTurno): ?array {
    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => $profesionalId],
        ['estado' => 'Activa', 'duracionturno' => 30]
    );

    $agenda->load(['diaDeAtencion.dias', 'diaDeAtencion.horariosAtenciones']);

    $duracion = (int) ($agenda->duracionturno ?? 30);

    $numeroDiaSemana = [
        'domingo' => 0,
        'lunes' => 1,
        'martes' => 2,
        'miercoles' => 3,
        'miércoles' => 3,
        'jueves' => 4,
        'viernes' => 5,
        'sabado' => 6,
        'sábado' => 6,
    ];

    $horariosPorDia = [];
    foreach ($agenda->diaDeAtencion ?? [] as $diaAtencion) {
        $diaDesc = mb_strtolower(trim((string) ($diaAtencion->dias?->descripcion ?? '')));
        $diaNumero = $numeroDiaSemana[$diaDesc] ?? null;

        if ($diaNumero === null) {
            continue;
        }

        foreach ($diaAtencion->horariosAtenciones ?? [] as $horario) {
            if (!$horario->horariocomienzo || !$horario->horariofin) {
                continue;
            }

            $horariosPorDia[$diaNumero][] = [
                'inicio' => substr((string) $horario->horariocomienzo, 0, 8),
                'fin' => substr((string) $horario->horariofin, 0, 8),
            ];
        }
    }

    $diasPreferidos = collect($formulario->diasPreferidos ?? [])
        ->map(fn ($dia) => mb_strtolower(trim((string) ($dia->descripcion ?? ''))))
        ->filter()
        ->values();

    $diasPermitidos = null;
    if ($diasPreferidos->isNotEmpty() && !$diasPreferidos->contains('indistinto')) {
        $diasPermitidos = $diasPreferidos
            ->map(fn ($dia) => $numeroDiaSemana[$dia] ?? null)
            ->filter(fn ($diaNumero) => $diaNumero !== null)
            ->unique()
            ->values()
            ->all();
    }

    $horariosPreferidos = collect($formulario->horariosPreferidos ?? [])
        ->map(fn ($horario) => mb_strtolower(trim((string) ($horario->descripcion ?? ''))))
        ->filter()
        ->values();

    $preferenciaAM = $horariosPreferidos->contains('am');
    $preferenciaPM = $horariosPreferidos->contains('pm');
    $preferenciaIndistinto = $horariosPreferidos->contains('indistinto');

    $cumpleHorarioPreferido = function (\Illuminate\Support\Carbon $inicioTurno) use ($preferenciaAM, $preferenciaPM, $preferenciaIndistinto): bool {
        if ($preferenciaIndistinto || (!$preferenciaAM && !$preferenciaPM) || ($preferenciaAM && $preferenciaPM)) {
            return true;
        }

        $hora = (int) $inicioTurno->format('H');

        if ($preferenciaAM) {
            return $hora < 12;
        }

        if ($preferenciaPM) {
            return $hora >= 12;
        }

        return true;
    };

    $inicioBusqueda = now()->addDay()->startOfDay();
    $finBusqueda = now()->addDays(90)->endOfDay();
    $cursor = $inicioBusqueda->copy();

    while ($cursor->lte($finBusqueda)) {
        $diaSemana = (int) $cursor->dayOfWeek;

        if (is_array($diasPermitidos) && !in_array($diaSemana, $diasPermitidos, true)) {
            $cursor->addDay();
            continue;
        }

        $horariosDelDia = $horariosPorDia[$diaSemana] ?? [];

        usort($horariosDelDia, fn ($a, $b) => strcmp((string) $a['inicio'], (string) $b['inicio']));

        foreach ($horariosDelDia as $rango) {
            $inicioRango = \Illuminate\Support\Carbon::parse($cursor->format('Y-m-d') . ' ' . $rango['inicio']);
            $finRango = \Illuminate\Support\Carbon::parse($cursor->format('Y-m-d') . ' ' . $rango['fin']);

            $slot = $inicioRango->copy();

            while ($slot->copy()->addMinutes($duracion)->lte($finRango)) {
                if ($slot->lt($inicioBusqueda)) {
                    $slot->addMinutes($duracion);
                    continue;
                }

                if (!$cumpleHorarioPreferido($slot)) {
                    $slot->addMinutes($duracion);
                    continue;
                }

                $errorDisponibilidad = $verificarDisponibilidadTurno((int) $agenda->id, $duracion, $slot->copy());
                if ($errorDisponibilidad === null) {
                    return [
                        'agenda_id' => (int) $agenda->id,
                        'duracion' => $duracion,
                        'inicio' => $slot->copy(),
                        'fecha' => $slot->format('Y-m-d'),
                        'hora' => $slot->format('H:i'),
                    ];
                }

                $slot->addMinutes($duracion);
            }
        }

        $cursor->addDay();
    }

    return null;
};

Route::get('/', function () {
    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $quienesSomos = $contenidoWeb?->quienessomos;
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));

    $profesionales = Profesional::with(['persona.Foto', 'profesion', 'profesiones'])
        ->where('baja_id', 1)
        ->get()
        ->groupBy('persona_id')
        ->map(function ($profesionalesPorPersona) {
            $profesionalBase = $profesionalesPorPersona->first();

            $profesionesCombinadas = $profesionalesPorPersona
                ->flatMap(function ($profesional) {
                    return $profesional->profesiones
                        ->push($profesional->profesion)
                        ->filter();
                })
                ->unique('id')
                ->values();

            $profesionalBase->setRelation('profesiones', $profesionesCombinadas);

            return $profesionalBase;
        })
        ->values();

    $servicios = Servicio::with('foto')
        ->where('estado', 'activo')
        ->where('visibleenweb', 'si')
        ->get();

    $profesiones = Profesion::query()
        ->where('visible_en_formulario', true)
        ->orderBy('titulo')
        ->get();

    $modalidades = Modalidad::query()
        ->orderBy('descripcion')
        ->get();

    $mensajeBurbujaAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_BURBUJA)
        ->value('respuesta') ?? '¡Hola, soy Clara!, ¿te puedo ayudar en algo?';

    $mensajeInicioPanelAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_PANEL_INICIO)
        ->value('respuesta') ?? 'Hola, soy Clara, la asistente virtual de Abogadas del Litoral. Escribí una palabra clave con el tema con el que necesites ayuda.';

    $nombreAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_NOMBRE)
        ->value('respuesta') ?? 'Clara';

    $chipsAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_CHIPS)
        ->orderBy('orden')
        ->orderBy('id')
        ->pluck('respuesta')
        ->flatMap(fn ($respuesta) => explode("\n", (string) $respuesta))
        ->map(fn ($c) => trim($c))
        ->filter(fn ($c) => $c !== '')
        ->unique()
        ->values()
        ->all();

    return view('welcome', [
        'quienesSomos'  => $quienesSomos,
        'versionSitio' => $versionSitio,
        'profesionales' => $profesionales,
        'servicios'     => $servicios,
        'profesiones'   => $profesiones,
        'modalidades'   => $modalidades,
        'mensajeBurbujaAsistente' => $mensajeBurbujaAsistente,
        'mensajeInicioPanelAsistente' => $mensajeInicioPanelAsistente,
        'nombreAsistente' => $nombreAsistente,
        'chipsAsistente' => $chipsAsistente,
    ]);
});

Route::get('/instrucciones-uso', function () {
    return view('instrucciones-uso');
});

Route::post('/asistente/chat', function (Request $request) {
    $validated = $request->validate([
        'mensaje' => ['required', 'string', 'max:500'],
    ]);

    $mensaje = mb_strtolower(trim((string) $validated['mensaje']));

    $mensajeError = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_ERROR)
        ->value('respuesta')
        ?? 'Lo siento, no tengo una respuesta exacta para eso.';

    $respuesta = $mensajeError;
    $encontrado = false;

    try {
        $faqs = FaqAsistente::query()
            ->where('activo', true)
            ->whereNotIn('intencion', [
                FAQ_INTENCION_BURBUJA,
                FAQ_INTENCION_PANEL_INICIO,
                FAQ_INTENCION_ERROR,
                FAQ_INTENCION_NOMBRE,
                FAQ_INTENCION_CHIPS,
            ])
            ->orderBy('orden')
            ->orderBy('id')
            ->get();

        foreach ($faqs as $faq) {
            $palabrasClave = collect($faq->palabras_clave)
                ->filter(fn ($item) => is_string($item) && trim($item) !== '')
                ->map(fn ($item) => mb_strtolower(trim($item)))
                ->values();

            $coincide = $palabrasClave->contains(fn ($keyword) => str_contains($mensaje, $keyword));

            if ($coincide) {
                $respuesta = (string) $faq->respuesta;
                $encontrado = true;
                break;
            }
        }
    } catch (QueryException $e) {
        // Si la tabla aún no existe, mantener fallback por defecto.
    }

    if (str_contains($respuesta, '{cantidad_servicios}')) {
        $cantidadServicios = Servicio::query()
            ->where('estado', 'activo')
            ->where('visibleenweb', 'si')
            ->count();

        $respuesta = str_replace('{cantidad_servicios}', (string) $cantidadServicios, $respuesta);
    }

    if (str_contains($respuesta, '{cantidad_profesionales}')) {
        $cantidadProfesionales = Profesional::query()
            ->where('baja_id', 1)
            ->count();

        $respuesta = str_replace('{cantidad_profesionales}', (string) $cantidadProfesionales, $respuesta);
    }

    if (!$encontrado) {
        try {
            AsistenteSinRespuesta::create(['consulta' => trim((string) $validated['mensaje'])]);
        } catch (QueryException $e) {
            // Tabla aún no migrada.
        }
    }

    return response()->json([
        'respuesta' => $respuesta,
    ]);
})->middleware('throttle:asistente-chat');

Route::get('/reportar-falla', function (Request $request) {
    $origen = trim((string) $request->query('origen', ''));

    return view('reportar-falla', [
        'origen' => $origen,
    ]);
});

Route::post('/reportar-falla', function (Request $request) {
    $validated = $request->validate([
        'titulo' => ['required', 'string', 'max:255'],
        'descripcion' => ['required', 'string', 'max:4500'],
        'origen' => ['nullable', 'string', 'max:1000'],
        'foto' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
        'captura_pantalla' => ['nullable', 'string', 'max:4000000'],
    ]);

    $descripcion = trim($validated['descripcion']);
    $origen = trim((string) ($validated['origen'] ?? ''));
    $fotoBugId = null;

    if ($origen !== '') {
        $descripcion .= "\n\nOrigen: " . $origen;
    }

    $fotoFile = $request->file('foto');
    if ($fotoFile) {
        $directorio = public_path('images/bugs');
        File::ensureDirectoryExists($directorio);

        $extension = strtolower((string) $fotoFile->getClientOriginalExtension());
        $mimeType = (string) $fotoFile->getClientMimeType();
        $tamanioBytes = (int) $fotoFile->getSize();
        $nombreArchivo = now()->format('YmdHis') . '_' . uniqid('bug_', true) . '.' . $extension;

        $fotoFile->move($directorio, $nombreArchivo);

        $fotoBug = FotoBug::create([
            'extension' => $extension,
            'tamanio_bytes' => $tamanioBytes,
            'nombre' => $nombreArchivo,
            'mime_type' => $mimeType,
        ]);

        $fotoBugId = $fotoBug->id;
    }

    // Si no se adjuntó foto manual pero hay captura automática de pantalla, usarla.
    if ($fotoBugId === null) {
        $capturaBase64 = trim((string) ($validated['captura_pantalla'] ?? ''));
        if ($capturaBase64 !== '' && str_starts_with($capturaBase64, 'data:image/')) {
            try {
                $partes = explode(',', $capturaBase64, 2);
                $datosImagen = base64_decode($partes[1] ?? '');
                if ($datosImagen !== false && strlen($datosImagen) > 0) {
                    $directorio = public_path('images/bugs');
                    File::ensureDirectoryExists($directorio);
                    $nombreArchivo = now()->format('YmdHis') . '_' . uniqid('cap_', true) . '.jpg';
                    file_put_contents($directorio . '/' . $nombreArchivo, $datosImagen);

                    $fotoBug = FotoBug::create([
                        'extension' => 'jpg',
                        'tamanio_bytes' => strlen($datosImagen),
                        'nombre' => $nombreArchivo,
                        'mime_type' => 'image/jpeg',
                    ]);

                    $fotoBugId = $fotoBug->id;
                }
            } catch (\Throwable) {
                // Si falla, continuar sin captura.
            }
        }
    }

    Bug::create([
        'titulo' => $validated['titulo'],
        'descripcion' => $descripcion,
        'prioridad' => 'Media',
        'estado' => 'Pendiente',
        'version' => 'Web',
        'fotobug_id' => $fotoBugId,
    ]);

    return redirect('/reportar-falla' . ($origen !== '' ? '?origen=' . urlencode($origen) : ''))
        ->with('bug_success', 'La falla fue reportada correctamente.');
})->middleware('throttle:reportar-bugs');

Route::post('/formulario', function (Request $request) use ($obtenerProfesionalesActivosCompatibles, $honeypotValido, $resolverProfesionalCompatible) {
    $validated = $request->validate([
        'nombre'         => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido'       => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'correo'         => ['required', 'email', 'max:255'],
        'celular'        => ['required', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'profesion_id'   => ['required', 'exists:profesiones,id'],
        'servicio_id'    => [
            'required',
            Rule::exists('servicios', 'id')->where(fn ($query) => $query
                ->where('profesion_id', $request->input('profesion_id'))
                ->where('visibleenweb', 'si')
                ->where('estado', 'activo')),
        ],
        'profesional_id' => [
            'required',
            function (string $attribute, mixed $value, \Closure $fail) use ($request, $resolverProfesionalCompatible): void {
                if ($value === 'INDISTINTO') {
                    return;
                }

                $profesionalCompatible = $resolverProfesionalCompatible((int) $value, (int) $request->input('profesion_id'));

                if (!$profesionalCompatible) {
                    $fail('El profesional seleccionado no es valido para la profesion elegida.');
                }
            },
        ],
        'modalidad_id'   => ['nullable'],
        'dias_preferencia'   => [
            'required',
            'array',
            'min:1',
            function (string $attribute, mixed $value, \Closure $fail): void {
                if (!is_array($value)) {
                    return;
                }

                if (in_array('Indistinto', $value, true) && count($value) > 1) {
                    $fail('No podes elegir "Indistinto" junto con otros dias.');
                }
            },
        ],
        'dias_preferencia.*' => ['required', 'string', 'in:Lunes,Martes,Miercoles,Jueves,Viernes,Indistinto'],
        'horario_preferencia' => ['required', 'string', 'in:AM,PM,INDISTINTO'],
        'descripcion'    => ['required', 'string', 'max:3000'],
        'website'        => ['nullable', 'string', 'max:255'],
    ]);

    if (!$honeypotValido($request)) {
        return redirect('/#formulario')
            ->withErrors(['formulario' => 'No se pudo enviar el formulario.'])
            ->withInput();
    }

    $profesionalSeleccionadoId = null;
    if ($validated['profesional_id'] !== 'INDISTINTO') {
        $profesionalCompatible = $resolverProfesionalCompatible((int) $validated['profesional_id'], (int) $validated['profesion_id']);
        $profesionalSeleccionadoId = $profesionalCompatible ? (int) $profesionalCompatible->id : null;
    }

    $formulario = Formulario::create([
        'nombrecompleto' => trim($validated['nombre'] . ' ' . $validated['apellido']),
        'correo'         => $validated['correo'],
        'celular'        => $validated['celular'],
        'ip_origen'      => (string) $request->ip(),
        'profesion_id'   => (int) $validated['profesion_id'],
        'servicio_id'    => (int) $validated['servicio_id'],
        'profesional_id' => $profesionalSeleccionadoId,
        'modalidad_id'   => 1,
        'descripcion'    => $validated['descripcion'],
        'fechaenvio'     => now()->toDateString(),
    ]);

    $diasIds = collect($validated['dias_preferencia'])
        ->map(fn ($descripcion) => DiaPreferencia::firstOrCreate(['descripcion' => $descripcion])->id)
        ->values()
        ->all();

    $horarioId = HorarioPreferencia::firstOrCreate([
        'descripcion' => $validated['horario_preferencia'],
    ])->id;

    $formulario->diasPreferidos()->sync($diasIds);
    $formulario->horariosPreferidos()->sync([$horarioId]);

    $profesionalesIds = $obtenerProfesionalesActivosCompatibles((int) $validated['profesion_id']);
    $asignaciones = $profesionalesIds
        ->map(fn ($profesionalId) => [
            'profesional_id' => (int) $profesionalId,
            'formulario_id' => (int) $formulario->id,
            'estado' => 'Pendiente',
        ])
        ->values()
        ->all();

    if (!empty($asignaciones)) {
        DB::table('profesionales_formularios')->insertOrIgnore($asignaciones);
    }

    return redirect('/#formulario')->with('form_success', 'Formulario enviado correctamente.');
});

Route::post('/cliente/formulario', function (Request $request) use ($obtenerProfesionalesActivosCompatibles, $honeypotValido, $resolverProfesionalCompatible) {
    if (!$request->session()->get('cliente_auth')) {
        return redirect('/login/cliente')->with('login_error', 'Debes iniciar sesión como cliente.');
    }

    $validated = $request->validate([
        'profesion_id'   => ['required', 'exists:profesiones,id'],
        'servicio_id'    => [
            'required',
            Rule::exists('servicios', 'id')->where(fn ($query) => $query
                ->where('profesion_id', $request->input('profesion_id'))
                ->where('visibleenweb', 'si')
                ->where('estado', 'activo')),
        ],
        'profesional_id' => [
            'required',
            function (string $attribute, mixed $value, \Closure $fail) use ($request, $resolverProfesionalCompatible): void {
                if ($value === 'INDISTINTO') {
                    return;
                }

                $profesionalCompatible = $resolverProfesionalCompatible((int) $value, (int) $request->input('profesion_id'));

                if (!$profesionalCompatible) {
                    $fail('El profesional seleccionado no es valido para la profesion elegida.');
                }
            },
        ],
        'modalidad_id'   => ['nullable'],
        'dias_preferencia'   => [
            'required',
            'array',
            'min:1',
            function (string $attribute, mixed $value, \Closure $fail): void {
                if (!is_array($value)) {
                    return;
                }

                if (in_array('Indistinto', $value, true) && count($value) > 1) {
                    $fail('No podes elegir "Indistinto" junto con otros dias.');
                }
            },
        ],
        'dias_preferencia.*' => ['required', 'string', 'in:Lunes,Martes,Miercoles,Jueves,Viernes,Indistinto'],
        'horario_preferencia' => ['required', 'string', 'in:AM,PM,INDISTINTO'],
        'descripcion'    => ['required', 'string', 'max:3000'],
        'website'        => ['nullable', 'string', 'max:255'],
    ]);

    if (!$honeypotValido($request)) {
        return redirect('/cliente/dashboard#formulario')
            ->withErrors(['formulario' => 'No se pudo enviar el formulario.'])
            ->withInput();
    }

    $clienteCorreoSesion = trim((string) $request->session()->get('cliente_correo', ''));

    $cliente = Cliente::query()
        ->with(['persona.telefonos', 'credencialCliente'])
        ->whereHas('credencialCliente', fn ($query) => $query->where('correo', $clienteCorreoSesion))
        ->first();

    $nombre = trim((string) ($cliente?->persona?->nombre ?? $request->session()->get('cliente_nombre', '')));
    $apellido = trim((string) ($cliente?->persona?->apellido ?? $request->session()->get('cliente_apellido', '')));
    $correo = trim((string) ($cliente?->correo ?? $cliente?->credencialCliente?->correo ?? $clienteCorreoSesion));
    $celular = trim((string) ($cliente?->persona?->telefonos?->first()?->telefono ?? $request->session()->get('cliente_celular', '')));

    if (!$cliente || !$cliente->id) {
        return redirect('/cliente/dashboard#formulario')
            ->with('form_error', 'No se pudo identificar tu cuenta de cliente. Volvé a iniciar sesión.');
    }

    if ($nombre === '' || $apellido === '' || $correo === '' || $celular === '') {
        return redirect('/cliente/dashboard#formulario')
            ->with('form_error', 'No se pudieron completar automaticamente tus datos personales. Verificá tus datos de cliente.');
    }

    $profesionalSeleccionadoId = null;
    if ($validated['profesional_id'] !== 'INDISTINTO') {
        $profesionalCompatible = $resolverProfesionalCompatible((int) $validated['profesional_id'], (int) $validated['profesion_id']);
        $profesionalSeleccionadoId = $profesionalCompatible ? (int) $profesionalCompatible->id : null;
    }

    $formulario = Formulario::create([
        'nombrecompleto' => trim($nombre . ' ' . $apellido),
        'correo'         => $correo,
        'celular'        => $celular,
        'ip_origen'      => (string) $request->ip(),
        'profesion_id'   => (int) $validated['profesion_id'],
        'servicio_id'    => (int) $validated['servicio_id'],
        'profesional_id' => $profesionalSeleccionadoId,
        'modalidad_id'   => 1,
        'cliente_id'     => (int) $cliente->id,
        'descripcion'    => $validated['descripcion'],
        'fechaenvio'     => now()->toDateString(),
    ]);

    $diasIds = collect($validated['dias_preferencia'])
        ->map(fn ($descripcion) => DiaPreferencia::firstOrCreate(['descripcion' => $descripcion])->id)
        ->values()
        ->all();

    $horarioId = HorarioPreferencia::firstOrCreate([
        'descripcion' => $validated['horario_preferencia'],
    ])->id;

    $formulario->diasPreferidos()->sync($diasIds);
    $formulario->horariosPreferidos()->sync([$horarioId]);

    $profesionalesIds = $obtenerProfesionalesActivosCompatibles((int) $validated['profesion_id']);
    $asignaciones = $profesionalesIds
        ->map(fn ($profesionalId) => [
            'profesional_id' => (int) $profesionalId,
            'formulario_id' => (int) $formulario->id,
            'estado' => 'Pendiente',
        ])
        ->values()
        ->all();

    if (!empty($asignaciones)) {
        DB::table('profesionales_formularios')->insertOrIgnore($asignaciones);
    }

    return redirect('/cliente/dashboard#formulario')->with('form_success', 'Formulario enviado correctamente.');
});

Route::get('/login/cliente', function (Request $request) {
    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));
    $intentosRestantes = max(0, RateLimiter::remaining(md5('login-cliente-web' . $request->ip()), 5));

    return view('auth.login-cliente', [
        'versionSitio' => $versionSitio,
        'intentosRestantes' => $intentosRestantes,
        'intentosMaximos' => 5,
    ]);
});

Route::get('/login/personal', function (Request $request) {
    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));
    $intentosRestantes = max(0, RateLimiter::remaining(md5('login-personal-web' . $request->ip()), 5));

    return view('auth.login-personal', [
        'versionSitio' => $versionSitio,
        'intentosRestantes' => $intentosRestantes,
        'intentosMaximos' => 5,
    ]);
});
Route::post('/login/cliente', [AuthController::class, 'loginClienteWeb'])->middleware('throttle:login-cliente-web');
Route::post('/login/personal', [AuthController::class, 'loginPersonalWeb'])->middleware('throttle:login-personal-web');

Route::get('/cliente/recuperar-credenciales', function (Request $request) {
    $intentosRestantes = max(0, RateLimiter::remaining(md5('recuperar-cliente' . $request->ip()), 5));

    return view('auth.recuperar-credenciales', [
        'intentosRestantes' => $intentosRestantes,
        'intentosMaximos' => 5,
    ]);
});

Route::post('/cliente/recuperar-credenciales', function (Request $request) use ($honeypotValido, $registrarLogSeguridadDirecto, $hashearTokenRecuperacion) {
    $request->validate([
        'correo' => ['required', 'email', 'max:255'],
        'website' => ['nullable', 'string', 'max:255'],
    ]);

    if (!$honeypotValido($request)) {
        return back()->withErrors(['recuperar_error' => 'No se pudo procesar la solicitud.'])->withInput($request->except(['website']));
    }

    $correo = trim((string) $request->input('correo'));
    $credencial = CredencialCliente::where('correo', $correo)->first();

    if ($credencial) {
        $token = Str::random(64);
        $credencial->update([
            'reset_token' => $hashearTokenRecuperacion($token),
            'reset_expira_en' => now()->addMinutes(30),
        ]);

        $registrarLogSeguridadDirecto(
            (int) ($credencial->cliente?->persona_id ?? 0),
            'CLIENTE',
            (string) $request->ip(),
            'Solicitud cambio de contraseña',
            'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' solicitó un cambio de contraseña.'
        );

        $link = url('/cliente/recuperar-credenciales/' . $token);
        $cuerpo = "Hola,\n\nRecibimos una solicitud para restablecer tu contraseña.\n"
            . "Hacé clic en el siguiente enlace para crear una nueva (válido por 30 minutos):\n\n"
            . $link . "\n\n"
            . "Si no solicitaste este cambio, ignorá este mensaje.";

        try {
            Mail::raw($cuerpo, function ($message) use ($correo): void {
                $message->to($correo)->subject('Restablecer contraseña - Abogadas del Litoral');
            });
        } catch (\Throwable $e) {
            logger()->warning('No se pudo enviar correo de recuperación.', [
                'correo' => $correo,
                'error' => $e->getMessage(),
            ]);
        }
    }

    // Siempre mostrar el mismo mensaje para no revelar si el correo existe
    return back()->with('recuperar_success', 'Si el correo está registrado, recibirás un enlace para restablecer tu contraseña en los próximos minutos.');
})->middleware('throttle:recuperar-cliente');

Route::get('/cliente/recuperar-credenciales/{token}', function (string $token) use ($buscarCredencialClientePorResetToken) {
    $credencial = $buscarCredencialClientePorResetToken($token);

    if (!$credencial) {
        return redirect('/cliente/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    return view('auth.nueva-contrasena', ['token' => $token]);
});

Route::post('/cliente/recuperar-credenciales/{token}', function (Request $request, string $token) use ($registrarLogSeguridadDirecto, $buscarCredencialClientePorResetToken) {
    $credencial = $buscarCredencialClientePorResetToken($token);

    if (!$credencial) {
        return redirect('/cliente/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    $request->validate([
        'contra' => ['required', 'string', 'min:6', 'max:30', 'confirmed'],
    ]);

    $credencial->update([
        'contra' => Hash::make((string) $request->input('contra')),
        'reset_token' => null,
        'reset_expira_en' => null,
    ]);

    $registrarLogSeguridadDirecto(
        (int) ($credencial->cliente?->persona_id ?? 0),
        'CLIENTE',
        (string) $request->ip(),
        'Cambio de contraseña exitoso',
        'El cliente ID ' . (int) ($credencial->cliente?->id ?? 0) . ' cambió su contraseña correctamente.'
    );

    return redirect('/login/cliente')
        ->with('login_success', 'Tu contraseña se actualizó correctamente. Ya podés iniciar sesión.');
});

Route::get('/personal/recuperar-credenciales', function (Request $request) {
    $intentosRestantes = max(0, RateLimiter::remaining(md5('recuperar-personal' . $request->ip()), 5));
    $profesiones = Profesion::query()->orderBy('titulo')->get(['id', 'titulo']);

    return view('auth.recuperar-credenciales-personal', [
        'intentosRestantes' => $intentosRestantes,
        'intentosMaximos' => 5,
        'profesiones' => $profesiones,
    ]);
});

Route::post('/personal/recuperar-credenciales', function (Request $request) use ($honeypotValido, $registrarLogSeguridadDirecto, $hashearTokenRecuperacion) {
    $request->validate([
        'dni' => ['required', 'string', 'max:20', 'regex:/^[0-9]+$/'],
        'matricula' => ['required', 'string', 'max:100'],
        'profesion_id' => ['required', 'integer', 'exists:profesiones,id'],
        'correo' => ['required', 'email', 'max:255'],
        'website' => ['nullable', 'string', 'max:255'],
    ]);

    if (!$honeypotValido($request)) {
        return back()->withErrors(['recuperar_error' => 'No se pudo procesar la solicitud.'])->withInput($request->except(['website']));
    }

    $dni = trim((string) $request->input('dni'));
    $matricula = trim((string) $request->input('matricula'));
    $profesionId = (int) $request->input('profesion_id');
    $correo = trim((string) $request->input('correo'));

    $profesional = Profesional::query()
        ->with('credencialProfesional')
        ->where('dni', $dni)
        ->where('matricula', $matricula)
        ->where('correo', $correo)
        ->where(function ($query) use ($profesionId): void {
            $query->where('profesion_id', $profesionId)
                ->orWhereHas('profesiones', function ($q) use ($profesionId): void {
                    $q->where('profesiones.id', $profesionId);
                });
        })
        ->first();

    if ($profesional?->credencialProfesional) {
        $token = Str::random(64);
        $profesional->credencialProfesional->update([
            'reset_token' => $hashearTokenRecuperacion($token),
            'reset_expira_en' => now()->addMinutes(30),
        ]);

        $registrarLogSeguridadDirecto(
            (int) ($profesional->persona_id ?? 0),
            'PROFESIONAL',
            (string) $request->ip(),
            'Solicitud cambio de contraseña',
            'El profesional ID ' . (int) $profesional->id . ' solicitó un cambio de contraseña.'
        );

        $link = url('/personal/recuperar-credenciales/' . $token);
        $cuerpo = "Hola,\n\nRecibimos una solicitud para restablecer tu contraseña.\n"
            . "Hacé clic en el siguiente enlace para crear una nueva (válido por 30 minutos):\n\n"
            . $link . "\n\n"
            . "Si no solicitaste este cambio, ignorá este mensaje.";

        try {
            Mail::raw($cuerpo, function ($message) use ($correo): void {
                $message->to($correo)->subject('Restablecer contraseña - Personal - Abogadas del Litoral');
            });
        } catch (\Throwable $e) {
            logger()->warning('No se pudo enviar correo de recuperación para personal.', [
                'correo' => $correo,
                'error' => $e->getMessage(),
            ]);
        }
    }

    return back()->with('recuperar_success', 'Si los datos coinciden con un profesional registrado, recibirás un enlace para restablecer tu contraseña en los próximos minutos.');
})->middleware('throttle:recuperar-personal');

Route::get('/personal/recuperar-credenciales/{token}', function (string $token) use ($buscarCredencialProfesionalPorResetToken) {
    $credencial = $buscarCredencialProfesionalPorResetToken($token);

    if (!$credencial) {
        return redirect('/personal/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    return view('auth.nueva-contrasena-personal', ['token' => $token]);
});

Route::post('/personal/recuperar-credenciales/{token}', function (Request $request, string $token) use ($registrarLogSeguridadDirecto, $buscarCredencialProfesionalPorResetToken) {
    $credencial = $buscarCredencialProfesionalPorResetToken($token);

    if (!$credencial) {
        return redirect('/personal/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    $request->validate([
        'contra' => ['required', 'string', 'min:6', 'max:30', 'confirmed'],
    ]);

    $credencial->update([
        'contra' => Hash::make((string) $request->input('contra')),
        'reset_token' => null,
        'reset_expira_en' => null,
    ]);

    $registrarLogSeguridadDirecto(
        (int) ($credencial->profesional?->persona_id ?? $credencial->administrador?->persona_id ?? 0),
        strtoupper((string) ($credencial->rol ?? 'PROFESIONAL')) === 'ADMIN' ? 'ADMIN' : 'PROFESIONAL',
        (string) $request->ip(),
        'Cambio de contraseña exitoso',
        'La cuenta personal ID ' . (int) $credencial->id . ' cambió su contraseña correctamente.'
    );

    return redirect('/login/personal')
        ->with('login_success', 'Tu contraseña se actualizó correctamente. Ya podés iniciar sesión.');
});

Route::get('/admin/recuperar-credenciales', function (Request $request) {
    $intentosRestantes = max(0, RateLimiter::remaining(md5('recuperar-admin' . $request->ip()), 5));

    return view('auth.recuperar-credenciales-admin', [
        'intentosRestantes' => $intentosRestantes,
        'intentosMaximos' => 5,
    ]);
});

Route::get('/admin/recuperar-credenciales/pregunta', function (Request $request) {
    $correo = trim((string) $request->query('correo', ''));

    if ($correo === '') {
        return response()->json([
            'encontrada' => false,
            'pregunta' => '',
        ]);
    }

    $admin = Administrador::query()
        ->with('credencial')
        ->where('correo', $correo)
        ->first();

    if (!$admin || strtoupper((string) ($admin->credencial?->rol ?? '')) !== 'ADMIN') {
        return response()->json([
            'encontrada' => false,
            'pregunta' => '',
        ]);
    }

    $preguntaGuardada = trim((string) ($admin->pregunta_secreta_hash ?? ''));
    $preguntaEsHash = Str::startsWith($preguntaGuardada, ['$2y$', '$2a$', '$2b$']);

    return response()->json([
        'encontrada' => !$preguntaEsHash && $preguntaGuardada !== '',
        'pregunta' => !$preguntaEsHash ? $preguntaGuardada : '',
    ]);
})->middleware('throttle:recuperar-admin-pregunta');

Route::post('/admin/recuperar-credenciales', function (Request $request) use ($honeypotValido, $registrarLogSeguridadDirecto, $hashearTokenRecuperacion) {
    $request->validate([
        'celular' => ['required', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'nombre' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'correo' => ['required', 'email', 'max:255'],
        'pregunta_secreta' => ['nullable', 'string', 'max:255'],
        'respuesta_secreta' => ['required', 'string', 'max:255'],
        'website' => ['nullable', 'string', 'max:255'],
    ]);

    if (!$honeypotValido($request)) {
        return back()->withErrors(['recuperar_error' => 'No se pudo procesar la solicitud.'])->withInput($request->except(['website']));
    }

    $correo = trim((string) $request->input('correo'));
    $nombre = mb_strtolower(trim((string) $request->input('nombre')));
    $apellido = mb_strtolower(trim((string) $request->input('apellido')));
    $celularNormalizado = preg_replace('/\D+/', '', (string) $request->input('celular'));
    $respuestaSecreta = mb_strtolower(trim((string) $request->input('respuesta_secreta')));

    $admin = Administrador::query()
        ->with(['persona.telefonos', 'credencial'])
        ->where('correo', $correo)
        ->first();

    if ($admin && $admin->credencial && strtoupper((string) ($admin->credencial->rol ?? '')) === 'ADMIN') {
        $nombreAdmin = mb_strtolower(trim((string) ($admin->persona?->nombre ?? '')));
        $apellidoAdmin = mb_strtolower(trim((string) ($admin->persona?->apellido ?? '')));
        $celularAdmin = preg_replace('/\D+/', '', (string) ($admin->persona?->telefonos?->first()?->telefono ?? ''));

        $preguntaGuardada = trim((string) ($admin->pregunta_secreta_hash ?? ''));
        $respuestaHash = (string) ($admin->respuesta_secreta_hash ?? '');

        $coincide = $nombre !== ''
            && $apellido !== ''
            && $celularNormalizado !== ''
            && $nombre === $nombreAdmin
            && $apellido === $apellidoAdmin
            && $celularNormalizado === $celularAdmin
            && $preguntaGuardada !== ''
            && $respuestaHash !== ''
            && Hash::check($respuestaSecreta, $respuestaHash);

        if ($coincide) {
            $token = Str::random(64);
            $admin->credencial->update([
                'reset_token' => $hashearTokenRecuperacion($token),
                'reset_expira_en' => now()->addMinutes(30),
            ]);

            $registrarLogSeguridadDirecto(
                (int) ($admin->persona_id ?? 0),
                'ADMIN',
                (string) $request->ip(),
                'Solicitud cambio de contraseña',
                'La administradora ID ' . (int) $admin->id . ' solicitó un cambio de contraseña.'
            );

            $link = url('/admin/recuperar-credenciales/' . $token);
            $cuerpo = "Hola,\n\nRecibimos una solicitud para restablecer tu contraseña de administrador.\n"
                . "Hacé clic en el siguiente enlace para crear una nueva (válido por 30 minutos):\n\n"
                . $link . "\n\n"
                . "Si no solicitaste este cambio, ignorá este mensaje.";

            try {
                Mail::raw($cuerpo, function ($message) use ($correo): void {
                    $message->to($correo)->subject('Restablecer contraseña - Administrador - Abogadas del Litoral');
                });
            } catch (\Throwable $e) {
                logger()->warning('No se pudo enviar correo de recuperación para administrador.', [
                    'correo' => $correo,
                    'error' => $e->getMessage(),
                ]);
            }
        }
    }

    return back()->with('recuperar_success', 'Si los datos coinciden con un administrador registrado, recibirás un enlace para restablecer tu contraseña en los próximos minutos.');
})->middleware('throttle:recuperar-admin');

Route::get('/admin/recuperar-credenciales/{token}', function (string $token) use ($buscarCredencialProfesionalPorResetToken) {
    $credencial = $buscarCredencialProfesionalPorResetToken($token, 'ADMIN');

    if (!$credencial) {
        return redirect('/admin/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    return view('auth.nueva-contrasena-admin', [
        'token' => $token,
        'usuarioActual' => (string) ($credencial->usuario ?? ''),
    ]);
});

Route::post('/admin/recuperar-credenciales/{token}', function (Request $request, string $token) use ($registrarLogSeguridadDirecto, $buscarCredencialProfesionalPorResetToken) {
    $credencial = $buscarCredencialProfesionalPorResetToken($token, 'ADMIN');

    if (!$credencial) {
        return redirect('/admin/recuperar-credenciales')
            ->with('recuperar_error', 'El enlace es inválido o ya expiró. Solicitá uno nuevo.');
    }

    $request->validate([
        'usuario' => ['required', 'string', 'max:100', Rule::unique('credencialesprofesionales', 'usuario')->ignore((int) $credencial->id)],
        'contra' => ['required', 'string', 'min:6', 'max:30', 'confirmed'],
    ]);

    $usuarioNuevo = trim((string) $request->input('usuario'));

    $credencial->update([
        'usuario' => $usuarioNuevo,
        'contra' => Hash::make((string) $request->input('contra')),
        'reset_token' => null,
        'reset_expira_en' => null,
    ]);

    $registrarLogSeguridadDirecto(
        (int) ($credencial->administrador?->persona_id ?? 0),
        'ADMIN',
        (string) $request->ip(),
        'Cambio de credenciales exitoso',
        'La cuenta administradora ID ' . (int) $credencial->id . ' actualizó su usuario y contraseña correctamente.'
    );

    return redirect('/login/personal')
        ->with('login_success', 'Tu usuario y contraseña de administrador se actualizaron correctamente.');
});

Route::post('/profesional/asignar-turno-directo', function (Request $request) use ($obtenerAdvertenciasTurno, $verificarConflictoTurno, $enviarCorreoTurno, $registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->with('profesiones')
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $profesionIdsProfesional = collect([(int) ($profesional->profesion_id ?? 0)])
        ->merge($profesional->profesiones->pluck('id')->map(fn ($id) => (int) $id))
        ->filter(fn ($id) => (int) $id > 0)
        ->unique()
        ->values()
        ->all();

    $validated = $request->validate([
        'fecha_turno'      => ['required', 'date', 'after_or_equal:today'],
        'hora_turno'       => ['required', 'date_format:H:i'],
        'nombrecompleto'   => ['required', 'string', 'max:200'],
        'correo'           => ['required', 'email', 'max:255'],
        'celular'          => ['required', 'string', 'regex:/^[0-9]{8,15}$/'],
        'descripcion'      => ['nullable', 'string', 'max:1000'],
        'servicio_id'      => [
            'required',
            Rule::exists('servicios', 'id')->where(function ($query) use ($profesionIdsProfesional) {
                $query->where('estado', 'activo');

                if (!empty($profesionIdsProfesional)) {
                    $query->whereIn('profesion_id', $profesionIdsProfesional);
                }
            }),
        ],
        'modalidad_id'     => ['required', 'exists:modalidades,id'],
        'omitir_restricciones' => ['nullable', 'boolean'],
    ]);

    $inicio = \Illuminate\Support\Carbon::parse($validated['fecha_turno'] . ' ' . $validated['hora_turno'] . ':00');

    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => (int) $profesional->id],
        ['estado' => 'activo', 'duracionturno' => 30]
    );

    $duracion = (int) ($agenda->duracionturno ?? 30);

    $advertencias = $obtenerAdvertenciasTurno((int) $agenda->id, $duracion, $inicio);
    $errorDisponibilidad = $verificarConflictoTurno((int) $agenda->id, $duracion, $inicio);

    if ($errorDisponibilidad !== null) {
        return redirect('/profesional/dashboard')
            ->with('turno_directo_error', 'Ese dia y horario no se encuentra disponible: ' . $errorDisponibilidad)
            ->withInput();
    }

    if (!empty($advertencias) && !((bool) ($validated['omitir_restricciones'] ?? false))) {
        return redirect('/profesional/dashboard')
            ->with('turno_directo_warning', [
                'advertencias' => $advertencias,
                'inputs' => [
                    'fecha_turno' => $validated['fecha_turno'],
                    'hora_turno' => $validated['hora_turno'],
                    'nombrecompleto' => $validated['nombrecompleto'],
                    'correo' => $validated['correo'],
                    'celular' => $validated['celular'],
                    'descripcion' => $validated['descripcion'] ?? '',
                    'servicio_id' => $validated['servicio_id'],
                    'modalidad_id' => $validated['modalidad_id'],
                ],
            ])
            ->withInput();
    }

    $estadoConfirmadoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['confirmado'])
        ->value('id') ?? 1);

    Turno::create([
        'inicio'          => $inicio,
        'correo'          => $validated['correo'],
        'celular'         => $validated['celular'],
        'nombrecompleto'  => $validated['nombrecompleto'],
        'descripcion'     => trim((string) ($validated['descripcion'] ?? '')) !== ''
            ? trim((string) $validated['descripcion'])
            : 'Turno asignado manualmente desde la agenda.',
        'cliente_id'      => null,
        'estadoturno_id'  => $estadoConfirmadoId,
        'agenda_id'       => (int) $agenda->id,
        'profesional_id'  => (int) $profesional->id,
        'servicio_id'     => (int) $validated['servicio_id'],
        'modalidad_id'    => (int) $validated['modalidad_id'],
    ]);

    $profesional->loadMissing('persona');
    $nombreProfesional = trim((string) (($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? '')));
    $servicioTitulo = (string) (Servicio::query()->where('id', (int) $validated['servicio_id'])->value('titulo') ?? 'Servicio');
    $modalidadDescripcion = (string) (Modalidad::query()->where('id', (int) $validated['modalidad_id'])->value('descripcion') ?? 'Modalidad');

    $enviarCorreoTurno(
        'asignacion',
        (string) $validated['correo'],
        (string) $validated['nombrecompleto'],
        $inicio->copy(),
        $servicioTitulo,
        $modalidadDescripcion,
        $nombreProfesional
    );

    $registrarLogSeguridadProfesional(
        $request,
        'Asignó un turno',
        'El profesional ID ' . (int) $profesional->id . ' asignó un turno para "' . $validated['nombrecompleto'] . '" el ' . $inicio->format('d/m/Y H:i') . '.'
    );

    return redirect('/profesional/dashboard')
        ->with('turno_directo_success', 'El turno se asignó correctamente.');
});

Route::post('/profesional/turnos/{turno}/cancelar', function (Request $request, Turno $turno) use ($enviarCorreoTurno, $registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    if ((int) $turno->profesional_id !== (int) $profesional->id) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'No podés cancelar un turno que no pertenece a tu agenda.');
    }

    $estadoCanceladoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])
        ->value('id') ?? 0);

    if ($estadoCanceladoId <= 0) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'No se encontró el estado Cancelado.');
    }

    if ((int) $turno->estadoturno_id === $estadoCanceladoId) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'El turno seleccionado ya estaba cancelado.');
    }

    $turno->update([
        'estadoturno_id' => $estadoCanceladoId,
    ]);

    $turno->loadMissing(['servicio', 'modalidad']);
    $profesional->loadMissing('persona');

    $nombreProfesional = trim((string) (($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? '')));
    $correoTurno = (string) ($turno->correo ?? '');
    $nombreTurno = (string) ($turno->nombrecompleto ?? '');
    $servicioTurno = (string) ($turno->servicio?->titulo ?? 'Servicio');
    $modalidadTurno = (string) ($turno->modalidad?->descripcion ?? 'Modalidad');
    $inicioTurno = $turno->inicio ? $turno->inicio->copy() : now();

    $enviarCorreoTurno(
        'cancelacion',
        $correoTurno,
        $nombreTurno,
        $inicioTurno,
        $servicioTurno,
        $modalidadTurno,
        $nombreProfesional
    );

    $registrarLogSeguridadProfesional(
        $request,
        'Canceló un turno',
        'El profesional ID ' . (int) $profesional->id . ' canceló el turno ID ' . (int) $turno->id . ' de "' . $nombreTurno . '".'
    );

    return redirect('/profesional/dashboard')
        ->with('profesional_action_success', 'El turno se canceló correctamente.');
});

Route::post('/profesional/turnos/{turno}/reprogramar', function (Request $request, Turno $turno) use ($obtenerAdvertenciasTurno, $verificarConflictoTurno, $enviarCorreoTurno, $registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    if ((int) $turno->profesional_id !== (int) $profesional->id) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'No podés reprogramar un turno que no pertenece a tu agenda.');
    }

    $validated = $request->validate([
        'fecha_turno' => ['required', 'date', 'after_or_equal:today'],
        'hora_turno' => ['required', 'date_format:H:i'],
        'omitir_restricciones' => ['nullable', 'boolean'],
    ]);

    $estadoCanceladoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])
        ->value('id') ?? 0);

    if ($estadoCanceladoId > 0 && (int) $turno->estadoturno_id === $estadoCanceladoId) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'No podés reprogramar un turno cancelado.');
    }

    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => (int) $profesional->id],
        ['estado' => 'activo', 'duracionturno' => 30]
    );

    $duracion = (int) ($agenda->duracionturno ?? 30);
    $inicio = \Illuminate\Support\Carbon::parse($validated['fecha_turno'] . ' ' . $validated['hora_turno'] . ':00');
    $advertencias = $obtenerAdvertenciasTurno((int) $agenda->id, $duracion, $inicio);
    $errorDisponibilidad = $verificarConflictoTurno((int) $agenda->id, $duracion, $inicio, (int) $turno->id);

    if ($errorDisponibilidad !== null) {
        return redirect('/profesional/dashboard')
            ->with('profesional_action_error', 'No se pudo reprogramar el turno: ' . $errorDisponibilidad);
    }

    if (!empty($advertencias) && !((bool) ($validated['omitir_restricciones'] ?? false))) {
        return redirect('/profesional/dashboard')
            ->with('reprogramar_turno_warning', [
                'advertencias' => $advertencias,
                'inputs' => [
                    'fecha_turno' => $validated['fecha_turno'],
                    'hora_turno' => $validated['hora_turno'],
                ],
                'turno_id' => (int) $turno->id,
            ]);
    }

    $estadoReprogramadoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['reprogramado'])
        ->value('id') ?? 0);

    $datosActualizar = [
        'inicio' => $inicio,
        'agenda_id' => (int) $agenda->id,
    ];

    if ($estadoReprogramadoId > 0) {
        $datosActualizar['estadoturno_id'] = $estadoReprogramadoId;
    }

    $turno->update($datosActualizar);

    $turno->loadMissing(['servicio', 'modalidad']);
    $profesional->loadMissing('persona');

    $nombreProfesional = trim((string) (($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? '')));
    $correoTurno = (string) ($turno->correo ?? '');
    $nombreTurno = (string) ($turno->nombrecompleto ?? '');
    $servicioTurno = (string) ($turno->servicio?->titulo ?? 'Servicio');
    $modalidadTurno = (string) ($turno->modalidad?->descripcion ?? 'Modalidad');

    $enviarCorreoTurno(
        'reprogramacion',
        $correoTurno,
        $nombreTurno,
        $inicio->copy(),
        $servicioTurno,
        $modalidadTurno,
        $nombreProfesional
    );

    $registrarLogSeguridadProfesional(
        $request,
        'Reprogramó un turno',
        'El profesional ID ' . (int) $profesional->id . ' reprogramó el turno ID ' . (int) $turno->id . ' de "' . $nombreTurno . '" para el ' . $inicio->format('d/m/Y H:i') . '.'
    );

    return redirect('/profesional/dashboard')
        ->with('profesional_action_success', 'El turno se reprogramó correctamente.');
});

Route::get('/profesional/dashboard', function () {
    $credencialId = (int) session('personal_credencial_id', 0);
    $profesionalSesion = Profesional::query()
        ->with(['profesion', 'profesiones'])
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesionalSesion) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $quienesSomos = $contenidoWeb?->quienessomos;
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));

    $profesionales = Profesional::with(['persona.Foto', 'profesion', 'profesiones'])
        ->where('baja_id', 1)
        ->get();

    $profesionIdsProfesional = collect([(int) ($profesionalSesion->profesion_id ?? 0)])
        ->merge($profesionalSesion->profesiones->pluck('id')->map(fn ($id) => (int) $id))
        ->filter(fn ($id) => (int) $id > 0)
        ->unique()
        ->values();

    $servicios = Servicio::with('foto')
        ->where('estado', 'activo')
        ->where('visibleenweb', 'si')
        ->when($profesionIdsProfesional->isNotEmpty(), function ($query) use ($profesionIdsProfesional) {
            $query->whereIn('profesion_id', $profesionIdsProfesional->all());
        })
        ->orderBy('titulo')
        ->get();

    $profesiones = Profesion::query()
        ->where('visible_en_formulario', true)
        ->orderBy('titulo')
        ->get();

    $modalidades = Modalidad::query()
        ->orderBy('descripcion')
        ->get();

    $turnos = Turno::query()
        ->with(['servicio', 'modalidad', 'estadoTurno', 'cliente.persona.telefonos'])
        ->where('profesional_id', (int) $profesionalSesion->id)
        ->orderBy('inicio')
        ->get();

    $agendaProfesional = Agenda::query()
        ->with(['diaDeAtencion.dias', 'diaDeAtencion.horariosAtenciones', 'diaDeAtencion.horariosRecesos', 'feriado', 'modoVacaciones'])
        ->where('profesional_id', (int) $profesionalSesion->id)
        ->first();

    $numeroDiaSemana = [
        'domingo' => 0,
        'lunes' => 1,
        'martes' => 2,
        'miercoles' => 3,
        'miércoles' => 3,
        'jueves' => 4,
        'viernes' => 5,
        'sabado' => 6,
        'sábado' => 6,
    ];

    $coloresEstado = [
        'confirmado' => '#198754',
        'cancelado' => '#dc3545',
        'reprogramado' => '#fd7e14',
        'cliente ausente' => '#6c757d',
        'cliente presente' => '#0d6efd',
    ];

    $duracionTurnoVisual = (int) ($agendaProfesional?->duracionturno ?? 30);

    $eventosAgenda = $turnos
        ->filter(fn ($turno) => $turno->inicio)
        ->filter(function ($turno) {
            $estadoDescripcion = mb_strtolower(trim((string) ($turno->estadoTurno?->descripcion ?? '')));

            return $estadoDescripcion !== 'cancelado';
        })
        ->map(function ($turno) use ($coloresEstado, $duracionTurnoVisual) {
            $estadoDescripcion = trim((string) ($turno->estadoTurno?->descripcion ?? 'Sin estado'));
            $estadoKey = mb_strtolower($estadoDescripcion);
            $color = $coloresEstado[$estadoKey] ?? '#0d6efd';

            // Naranja para turnos asignados manualmente (sin cliente vinculado y estado confirmado)
            if ($estadoKey === 'confirmado' && $turno->cliente_id === null) {
                $color = '#fd7e14';
            }

            $servicioTitulo = trim((string) ($turno->servicio?->titulo ?? 'Turno'));
            $nombreCompleto = trim((string) ($turno->nombrecompleto ?? 'Cliente'));
            $celularCliente = trim((string) ($turno->cliente?->persona?->telefonos?->first()?->telefono ?? ''));
            if ($celularCliente === '') {
                $celularCliente = trim((string) ($turno->celular ?? ''));
            }

            return [
                'id' => (int) $turno->id,
                'title' => $servicioTitulo . ' - ' . $nombreCompleto,
                'start' => $turno->inicio->format('Y-m-d\TH:i:s'),
                'end' => $turno->inicio->copy()->addMinutes($duracionTurnoVisual)->format('Y-m-d\TH:i:s'),
                'backgroundColor' => $color,
                'borderColor' => $color,
                'extendedProps' => [
                    'prioridad' => 4,
                    'turnoId' => (int) $turno->id,
                    'cliente' => $nombreCompleto,
                    'celular' => $celularCliente !== '' ? $celularCliente : '-',
                    'correo' => (string) ($turno->correo ?? ''),
                    'descripcion' => (string) ($turno->descripcion ?? ''),
                    'servicio' => $servicioTitulo,
                    'modalidad' => (string) ($turno->modalidad?->descripcion ?? '-'),
                    'estado' => $estadoDescripcion,
                    'estadoKey' => $estadoKey,
                    'puedeCancelarse' => $estadoKey !== 'cancelado',
                    'puedeReprogramarse' => $estadoKey !== 'cancelado',
                ],
            ];
        })
        ->values();

    $inicioRango = now()->startOfDay();
    $finRango = now()->addDays(90)->endOfDay();

    $eventosDisponibilidad = collect($agendaProfesional?->diaDeAtencion ?? [])
        ->flatMap(function ($diaAtencion) use ($numeroDiaSemana, $inicioRango, $finRango) {
            $diaDescripcion = mb_strtolower(trim((string) ($diaAtencion->dias?->descripcion ?? '')));
            $diaSemana = $numeroDiaSemana[$diaDescripcion] ?? null;

            if ($diaSemana === null) {
                return [];
            }

            return collect($diaAtencion->horariosAtenciones ?? [])
                ->filter(fn ($horario) => $horario->horariocomienzo && $horario->horariofin)
                ->flatMap(function ($horario) use ($diaSemana, $diaAtencion, $inicioRango, $finRango) {
                    $eventos = [];
                    $cursor = $inicioRango->copy();

                    while ($cursor->lte($finRango)) {
                        if ((int) $cursor->dayOfWeek === (int) $diaSemana) {
                            $fecha = $cursor->format('Y-m-d');
                            $horaInicio = substr((string) $horario->horariocomienzo, 0, 8);
                            $horaFin = substr((string) $horario->horariofin, 0, 8);

                            $eventos[] = [
                                'id' => 'disp-' . $diaAtencion->id . '-' . $horario->id . '-' . $fecha,
                                'title' => 'Disponibilidad',
                                'start' => $fecha . 'T' . $horaInicio,
                                'end' => $fecha . 'T' . $horaFin,
                                'display' => 'background',
                                'classNames' => ['evento-disponibilidad'],
                                'backgroundColor' => '#198754',
                                'borderColor' => '#198754',
                                'extendedProps' => [
                                    'prioridad' => 5,
                                    'tipoEvento' => 'disponibilidad',
                                    'estado' => 'Disponible',
                                    'modalidad' => '-',
                                    'servicio' => 'Horario de atención',
                                    'cliente' => '-',
                                    'correo' => '-',
                                    'descripcion' => 'Franja horaria configurada en tu agenda.',
                                ],
                            ];
                        }

                        $cursor->addDay();
                    }

                    return $eventos;
                });
        })
        ->values();

    $eventosReceso = collect($agendaProfesional?->diaDeAtencion ?? [])
        ->flatMap(function ($diaAtencion) use ($numeroDiaSemana, $inicioRango, $finRango) {
            $diaDescripcion = mb_strtolower(trim((string) ($diaAtencion->dias?->descripcion ?? '')));
            $diaSemana = $numeroDiaSemana[$diaDescripcion] ?? null;

            if ($diaSemana === null) {
                return [];
            }

            return collect($diaAtencion->horariosRecesos ?? [])
                ->filter(fn ($receso) => $receso->comienzo && $receso->fin)
                ->flatMap(function ($receso) use ($diaSemana, $diaAtencion, $inicioRango, $finRango) {
                    $eventos = [];
                    $cursor = $inicioRango->copy();

                    while ($cursor->lte($finRango)) {
                        if ((int) $cursor->dayOfWeek === (int) $diaSemana) {
                            $fecha = $cursor->format('Y-m-d');
                            $horaInicio = substr((string) $receso->comienzo, 0, 8);
                            $horaFin = substr((string) $receso->fin, 0, 8);
                            $descripcionReceso = trim((string) ($receso->descripcion ?? ''));
                            $tituloReceso = $descripcionReceso !== ''
                                ? 'Receso: ' . $descripcionReceso
                                : 'Receso';

                            $eventos[] = [
                                'id' => 'receso-' . $diaAtencion->id . '-' . $receso->id . '-' . $fecha,
                                'title' => $tituloReceso,
                                'start' => $fecha . 'T' . $horaInicio,
                                'end' => $fecha . 'T' . $horaFin,
                                'display' => 'background',
                                'classNames' => ['evento-receso'],
                                'textColor' => '#5b1f23',
                                'backgroundColor' => '#dc3545',
                                'borderColor' => '#dc3545',
                                'extendedProps' => [
                                    'prioridad' => 3,
                                    'tipoEvento' => 'no_disponible',
                                    'motivo' => 'Receso',
                                    'estado' => 'No disponible',
                                    'descripcion' => 'Franja horaria de receso configurada.',
                                ],
                            ];
                        }

                        $cursor->addDay();
                    }

                    return $eventos;
                });
        })
        ->values();

    $eventosVacaciones = collect($agendaProfesional?->modoVacaciones ?? [])
        ->filter(fn ($vacacion) => $vacacion->inicio && $vacacion->fin)
        ->map(function ($vacacion) {
            $inicio = (string) $vacacion->inicio;
            $finExclusivo = \Illuminate\Support\Carbon::parse((string) $vacacion->fin)->addDay()->format('Y-m-d');
            $descripcionVacacion = trim((string) ($vacacion->descripcion ?? ''));

            return [
                'id' => 'vacaciones-' . $vacacion->id,
                'title' => $descripcionVacacion !== ''
                    ? 'Vacaciones: ' . $descripcionVacacion
                    : 'Vacaciones',
                'start' => $inicio,
                'end' => $finExclusivo,
                'allDay' => true,
                'display' => 'background',
                'classNames' => ['evento-vacaciones'],
                'textColor' => '#5b1f23',
                'backgroundColor' => '#dc3545',
                'borderColor' => '#dc3545',
                'extendedProps' => [
                    'prioridad' => 2,
                    'tipoEvento' => 'no_disponible',
                    'motivo' => 'Vacaciones',
                    'estado' => 'No disponible',
                    'descripcion' => (string) ($vacacion->descripcion ?: 'Vacaciones'),
                ],
            ];
        })
        ->values();

    $eventosFeriados = collect($agendaProfesional?->feriado ?? [])
        ->filter(fn ($feriado) => $feriado->fecha)
        ->map(function ($feriado) {
            $fecha = (string) $feriado->fecha;
            $finExclusivo = \Illuminate\Support\Carbon::parse($fecha)->addDay()->format('Y-m-d');
            $descripcionFeriado = trim((string) ($feriado->descripcion ?? ''));

            return [
                'id' => 'feriado-' . $feriado->id,
                'title' => $descripcionFeriado !== ''
                    ? 'Feriado: ' . $descripcionFeriado
                    : 'Feriado',
                'start' => $fecha,
                'end' => $finExclusivo,
                'allDay' => true,
                'display' => 'background',
                'classNames' => ['evento-feriado'],
                'textColor' => '#5b1f23',
                'backgroundColor' => '#dc3545',
                'borderColor' => '#dc3545',
                'extendedProps' => [
                    'prioridad' => 1,
                    'tipoEvento' => 'no_disponible',
                    'motivo' => 'Feriado',
                    'estado' => 'No disponible',
                    'descripcion' => (string) ($feriado->descripcion ?: 'Feriado'),
                ],
            ];
        })
        ->values();

    $duracionTurnoAgenda = (int) ($agendaProfesional?->duracionturno ?? 30);

    $rangosTurnosOcupados = $turnos
        ->filter(fn ($turno) => $turno->inicio)
        ->filter(function ($turno) {
            $estadoDescripcion = mb_strtolower(trim((string) ($turno->estadoTurno?->descripcion ?? '')));

            return $estadoDescripcion !== 'cancelado';
        })
        ->map(function ($turno) use ($duracionTurnoAgenda) {
            $inicio = \Illuminate\Support\Carbon::parse((string) $turno->inicio);

            return [
                'inicio' => $inicio->copy(),
                'fin' => $inicio->copy()->addMinutes($duracionTurnoAgenda),
            ];
        })
        ->values();

    $rangosNoDisponibles = collect()
        ->concat($rangosTurnosOcupados)
        ->concat($eventosReceso)
        ->concat($eventosVacaciones)
        ->concat($eventosFeriados)
        ->map(function ($evento) {
            $inicio = isset($evento['start']) ? \Illuminate\Support\Carbon::parse((string) $evento['start']) : null;
            $fin = isset($evento['end']) ? \Illuminate\Support\Carbon::parse((string) $evento['end']) : null;

            if (isset($evento['inicio'], $evento['fin'])) {
                return ['inicio' => $evento['inicio']->copy(), 'fin' => $evento['fin']->copy()];
            }

            if (!$inicio || !$fin) {
                return null;
            }

            return ['inicio' => $inicio, 'fin' => $fin];
        })
        ->filter()
        ->values();

    $eventosDisponibilidad = $eventosDisponibilidad
        ->flatMap(function ($evento) use ($rangosNoDisponibles) {
            if (!isset($evento['start'], $evento['end'])) {
                return [$evento];
            }

            $inicioEvento = \Illuminate\Support\Carbon::parse((string) $evento['start']);
            $finEvento = \Illuminate\Support\Carbon::parse((string) $evento['end']);

            $rangosSolapados = $rangosNoDisponibles
                ->filter(function ($rango) use ($inicioEvento, $finEvento) {
                    return $inicioEvento->lt($rango['fin']) && $finEvento->gt($rango['inicio']);
                })
                ->map(function ($rango) use ($inicioEvento, $finEvento) {
                    $inicio = $rango['inicio']->copy()->greaterThan($inicioEvento) ? $rango['inicio']->copy() : $inicioEvento->copy();
                    $fin = $rango['fin']->copy()->lessThan($finEvento) ? $rango['fin']->copy() : $finEvento->copy();

                    return ['inicio' => $inicio, 'fin' => $fin];
                })
                ->values()
                ->all();

            if (empty($rangosSolapados)) {
                return [$evento];
            }

            usort($rangosSolapados, function ($a, $b) {
                if ($a['inicio']->equalTo($b['inicio'])) {
                    return 0;
                }

                return $a['inicio']->lessThan($b['inicio']) ? -1 : 1;
            });

            $segmentos = [
                ['inicio' => $inicioEvento->copy(), 'fin' => $finEvento->copy()],
            ];

            foreach ($rangosSolapados as $rango) {
                $nuevosSegmentos = [];

                foreach ($segmentos as $segmento) {
                    if ($segmento['fin']->lessThanOrEqualTo($rango['inicio']) || $segmento['inicio']->greaterThanOrEqualTo($rango['fin'])) {
                        $nuevosSegmentos[] = $segmento;
                        continue;
                    }

                    if ($segmento['inicio']->lt($rango['inicio'])) {
                        $nuevosSegmentos[] = [
                            'inicio' => $segmento['inicio']->copy(),
                            'fin' => $rango['inicio']->copy()->subSecond(),
                        ];
                    }

                    if ($segmento['fin']->gt($rango['fin'])) {
                        $nuevosSegmentos[] = [
                            'inicio' => $rango['fin']->copy()->addSecond(),
                            'fin' => $segmento['fin']->copy(),
                        ];
                    }
                }

                $segmentos = $nuevosSegmentos;
            }

            return collect($segmentos)
                ->filter(fn ($segmento) => $segmento['inicio']->lt($segmento['fin']))
                ->values()
                ->map(function ($segmento, $index) use ($evento) {
                    $eventoSegmentado = $evento;
                    $eventoSegmentado['id'] = (string) ($evento['id'] ?? 'disp') . '-seg-' . $index;
                    $eventoSegmentado['start'] = $segmento['inicio']->format('Y-m-d\TH:i:s');
                    $eventoSegmentado['end'] = $segmento['fin']->format('Y-m-d\TH:i:s');

                    return $eventoSegmentado;
                })
                ->all();
        })
        ->values();

    $eventosAgenda = collect()
        ->concat($eventosFeriados)
        ->concat($eventosVacaciones)
        ->concat($eventosReceso)
        ->concat($eventosAgenda)
        ->concat($eventosDisponibilidad)
        ->values();

    $turnosNoCancelados = $turnos->filter(function ($turno) {
        if (!$turno->inicio) {
            return false;
        }

        $estadoDescripcion = mb_strtolower(trim((string) ($turno->estadoTurno?->descripcion ?? '')));

        return $estadoDescripcion !== 'cancelado';
    });

    return view('profesional.dashboard', [
        'quienesSomos'      => $quienesSomos,
        'profesionales'     => $profesionales,
        'servicios'         => $servicios,
        'profesiones'       => $profesiones,
        'modalidades'       => $modalidades,
        'eventosAgenda'     => $eventosAgenda,
        'cantidadTurnosHoy' => $turnosNoCancelados
            ->filter(fn ($turno) => $turno->inicio && $turno->inicio->isToday())
            ->count(),
        'cantidadTurnosFuturos' => $turnosNoCancelados
            ->filter(fn ($turno) => $turno->inicio && $turno->inicio->greaterThan(now()->endOfDay()))
            ->count(),
    ]);
});

Route::get('/profesional/ayuda', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesionalSesionId = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->value('id');

    if (!$profesionalSesionId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    return view('profesional.ayuda');
});

Route::get('/profesional/clientes', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesionalSesionId = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->value('id');

    if (!$profesionalSesionId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $filtroTexto = trim((string) $request->query('q', ''));
    $filtroEstado = trim((string) $request->query('estado', ''));

    $clientes = Cliente::with('persona')
        ->whereHas('profesionales', fn ($q) => $q->where('profesionales.id', (int) $profesionalSesionId))
        ->when($filtroTexto !== '', function ($query) use ($filtroTexto) {
            $query->where(function ($subQuery) use ($filtroTexto): void {
                $subQuery->where('dni', 'like', '%' . $filtroTexto . '%')
                    ->orWhere('correo', 'like', '%' . $filtroTexto . '%')
                    ->orWhereHas('persona', function ($qPersona) use ($filtroTexto): void {
                        $qPersona->where('nombre', 'like', '%' . $filtroTexto . '%')
                            ->orWhere('apellido', 'like', '%' . $filtroTexto . '%')
                            ->orWhere('dni', 'like', '%' . $filtroTexto . '%');
                    });
            });
        })
        ->when($filtroEstado === 'activos', fn ($query) => $query->where('baja_id', 1))
        ->when($filtroEstado === 'inactivos', fn ($query) => $query->where('baja_id', '!=', 1))
        ->orderBy('id')
        ->paginate(10)
        ->withQueryString();

    return view('profesional.mis-clientes', [
        'clientes' => $clientes,
    ]);
});

Route::get('/profesional/mis-datos', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::with(['persona.Foto', 'profesion', 'credencialProfesional'])
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    return view('profesional.mis-datos', [
        'profesional' => $profesional,
    ]);
});

Route::get('/profesional/configurar-agenda', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => (int) $profesional->id],
        ['estado' => 'activo', 'duracionturno' => 30]
    );

    $diasCatalogo = Dia::query()->orderBy('id')->get(['id', 'descripcion']);

    $diasAtencion = DiaDeAtencion::query()
        ->with(['dias', 'horariosAtenciones', 'horariosRecesos'])
        ->where('agenda_id', (int) $agenda->id)
        ->get();

    $bloquesAtencion = $diasAtencion
        ->flatMap(function ($diaAtencion) {
            return $diaAtencion->horariosAtenciones->map(function ($horario) use ($diaAtencion) {
                return [
                    'dia_id' => (int) ($diaAtencion->dia_id ?? 0),
                    'horariocomienzo' => substr((string) $horario->horariocomienzo, 0, 5),
                    'horariofin' => substr((string) $horario->horariofin, 0, 5),
                    'tipo' => strtoupper((string) $horario->tipo),
                ];
            });
        })
        ->values();

    $bloquesReceso = $diasAtencion
        ->flatMap(function ($diaAtencion) {
            return $diaAtencion->horariosRecesos->map(function ($receso) use ($diaAtencion) {
                return [
                    'dia_id' => (int) ($diaAtencion->dia_id ?? 0),
                    'comienzo' => substr((string) $receso->comienzo, 0, 5),
                    'fin' => substr((string) $receso->fin, 0, 5),
                ];
            });
        })
        ->values();

    $vacaciones = ModoVacaciones::query()
        ->where('agenda_id', (int) $agenda->id)
        ->orderBy('inicio')
        ->get(['inicio', 'fin', 'descripcion'])
        ->map(fn ($v) => [
            'inicio' => (string) $v->inicio,
            'fin' => (string) $v->fin,
            'descripcion' => (string) $v->descripcion,
        ])
        ->values();

    $feriados = Feriado::query()
        ->where('agenda_id', (int) $agenda->id)
        ->orderBy('fecha')
        ->get(['fecha', 'descripcion'])
        ->map(fn ($f) => [
            'fecha' => (string) $f->fecha,
            'descripcion' => (string) $f->descripcion,
        ])
        ->values();

    return view('profesional.configurar-agenda', [
        'diasCatalogo' => $diasCatalogo,
        'bloquesAtencion' => $bloquesAtencion,
        'bloquesReceso' => $bloquesReceso,
        'vacaciones' => $vacaciones,
        'feriados' => $feriados,
        'duracionTurno' => (int) ($agenda->duracionturno ?? 30),
    ]);
});

Route::post('/profesional/configurar-agenda', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => (int) $profesional->id],
        ['estado' => 'activo', 'duracionturno' => 30]
    );

    $duracionTurno = (int) $request->input('duracionturno', $agenda->duracionturno ?? 30);

    $diaIdsValidos = Dia::query()->pluck('id')->map(fn ($id) => (int) $id)->all();
    $diaIdsLookup = array_flip($diaIdsValidos);

    $diasInput = collect($request->input('dias', []))
        ->map(function ($fila) {
            return [
                'dia_id' => (int) ($fila['dia_id'] ?? 0),
                'horariocomienzo' => trim((string) ($fila['horariocomienzo'] ?? '')),
                'horariofin' => trim((string) ($fila['horariofin'] ?? '')),
                'tipo' => strtoupper(trim((string) ($fila['tipo'] ?? ''))),
            ];
        })
        ->filter(fn ($fila) => $fila['dia_id'] > 0 || $fila['horariocomienzo'] !== '' || $fila['horariofin'] !== '' || $fila['tipo'] !== '')
        ->values();

    $recesosInput = collect($request->input('recesos', []))
        ->map(function ($fila) {
            return [
                'dia_id' => (int) ($fila['dia_id'] ?? 0),
                'comienzo' => trim((string) ($fila['comienzo'] ?? '')),
                'fin' => trim((string) ($fila['fin'] ?? '')),
            ];
        })
        ->filter(fn ($fila) => $fila['dia_id'] > 0 || $fila['comienzo'] !== '' || $fila['fin'] !== '')
        ->values();

    $vacacionesInput = collect($request->input('vacaciones', []))
        ->map(function ($fila) {
            return [
                'inicio' => trim((string) ($fila['inicio'] ?? '')),
                'fin' => trim((string) ($fila['fin'] ?? '')),
                'descripcion' => trim((string) ($fila['descripcion'] ?? '')),
            ];
        })
        ->filter(fn ($fila) => $fila['inicio'] !== '' || $fila['fin'] !== '' || $fila['descripcion'] !== '')
        ->values();

    $feriadosInput = collect($request->input('feriados', []))
        ->map(function ($fila) {
            return [
                'fecha' => trim((string) ($fila['fecha'] ?? '')),
                'descripcion' => trim((string) ($fila['descripcion'] ?? '')),
            ];
        })
        ->filter(fn ($fila) => $fila['fecha'] !== '' || $fila['descripcion'] !== '')
        ->values();

    $errores = [];

    if ($duracionTurno < 10 || $duracionTurno > 180 || $duracionTurno % 5 !== 0) {
        $errores['duracionturno'] = 'La duración del turno debe ser un número entre 10 y 180 minutos, en intervalos de 5.';
    }

    if ($diasInput->count() > 10) {
        $errores['dias'] = 'Solo puedes cargar hasta 10 horarios de atención.';
    }

    $cantidadAM = $diasInput->where('tipo', 'AM')->count();
    $cantidadPM = $diasInput->where('tipo', 'PM')->count();

    if ($cantidadAM > 5) {
        $errores['dias_am'] = 'Solo puedes cargar hasta 5 horarios de tipo AM.';
    }

    if ($cantidadPM > 5) {
        $errores['dias_pm'] = 'Solo puedes cargar hasta 5 horarios de tipo PM.';
    }

    foreach ($diasInput as $index => $fila) {
        if (!isset($diaIdsLookup[$fila['dia_id']])) {
            $errores['dias.' . $index . '.dia_id'] = 'El día seleccionado no es válido.';
        }

        if (!in_array($fila['tipo'], ['AM', 'PM'], true)) {
            $errores['dias.' . $index . '.tipo'] = 'El tipo debe ser AM o PM.';
        }

        if ($fila['horariocomienzo'] === '' || $fila['horariofin'] === '') {
            $errores['dias.' . $index . '.horario'] = 'Cada horario de atención debe tener inicio y fin.';
        } elseif ($fila['horariocomienzo'] >= $fila['horariofin']) {
            $errores['dias.' . $index . '.rango'] = 'El horario de inicio debe ser menor al de fin.';
        }
    }

    foreach ($recesosInput as $index => $fila) {
        if (!isset($diaIdsLookup[$fila['dia_id']])) {
            $errores['recesos.' . $index . '.dia_id'] = 'El día del receso no es válido.';
        }

        if ($fila['comienzo'] === '' || $fila['fin'] === '') {
            $errores['recesos.' . $index . '.horario'] = 'Cada receso debe tener horario de inicio y fin.';
        } elseif ($fila['comienzo'] >= $fila['fin']) {
            $errores['recesos.' . $index . '.rango'] = 'El receso debe tener inicio menor que fin.';
        }
    }

    foreach ($vacacionesInput as $index => $fila) {
        if ($fila['inicio'] === '' || $fila['fin'] === '') {
            $errores['vacaciones.' . $index . '.fechas'] = 'Cada vacaciones debe tener fecha de inicio y fin.';
            continue;
        }

        if ($fila['inicio'] > $fila['fin']) {
            $errores['vacaciones.' . $index . '.rango'] = 'La fecha de inicio de vacaciones no puede ser mayor al fin.';
        }
    }

    foreach ($feriadosInput as $index => $fila) {
        if ($fila['fecha'] === '') {
            $errores['feriados.' . $index . '.fecha'] = 'Cada feriado debe tener fecha.';
        }
    }

    if (!empty($errores)) {
        return back()->withErrors($errores)->withInput();
    }

    DB::transaction(function () use ($agenda, $duracionTurno, $diasInput, $recesosInput, $vacacionesInput, $feriadosInput): void {
        $agenda->update([
            'duracionturno' => $duracionTurno,
        ]);

        $diasExistentes = DiaDeAtencion::query()
            ->where('agenda_id', (int) $agenda->id)
            ->pluck('id')
            ->map(fn ($id) => (int) $id)
            ->all();

        if (!empty($diasExistentes)) {
            HorarioDeAtencion::query()->whereIn('diadeatencion_id', $diasExistentes)->delete();
            HorarioReceso::query()->whereIn('diadeatencion_id', $diasExistentes)->delete();
            DiaDeAtencion::query()->whereIn('id', $diasExistentes)->delete();
        }

        ModoVacaciones::query()->where('agenda_id', (int) $agenda->id)->delete();
        Feriado::query()->where('agenda_id', (int) $agenda->id)->delete();

        $diaAtencionPorDia = [];

        foreach ($diasInput as $fila) {
            $diaId = (int) $fila['dia_id'];

            if (!isset($diaAtencionPorDia[$diaId])) {
                $diaAtencionPorDia[$diaId] = DiaDeAtencion::create([
                    'agenda_id' => (int) $agenda->id,
                    'dia_id' => $diaId,
                ]);
            }

            HorarioDeAtencion::create([
                'horariocomienzo' => $fila['horariocomienzo'],
                'horariofin' => $fila['horariofin'],
                'tipo' => $fila['tipo'],
                'diadeatencion_id' => (int) $diaAtencionPorDia[$diaId]->id,
            ]);
        }

        foreach ($recesosInput as $fila) {
            $diaId = (int) $fila['dia_id'];

            if (!isset($diaAtencionPorDia[$diaId])) {
                $diaAtencionPorDia[$diaId] = DiaDeAtencion::create([
                    'agenda_id' => (int) $agenda->id,
                    'dia_id' => $diaId,
                ]);
            }

            HorarioReceso::create([
                'comienzo' => $fila['comienzo'],
                'fin' => $fila['fin'],
                'diadeatencion_id' => (int) $diaAtencionPorDia[$diaId]->id,
            ]);
        }

        foreach ($vacacionesInput as $fila) {
            ModoVacaciones::create([
                'inicio' => $fila['inicio'],
                'fin' => $fila['fin'],
                'descripcion' => $fila['descripcion'] !== '' ? $fila['descripcion'] : 'Vacaciones :)',
                'agenda_id' => (int) $agenda->id,
            ]);
        }

        foreach ($feriadosInput as $fila) {
            Feriado::create([
                'fecha' => $fila['fecha'],
                'descripcion' => $fila['descripcion'] !== '' ? $fila['descripcion'] : 'Dia no laborable',
                'agenda_id' => (int) $agenda->id,
            ]);
        }
    });

    return redirect('/profesional/configurar-agenda')->with('agenda_success', 'Agenda actualizada correctamente.');
});

Route::get('/profesional/clientes/crear', function (Request $request) {
    $dniConsultado = trim((string) $request->query('dni', old('dni', '')));
    $personaDni = null;
    $estadoDni = null;

    if ($dniConsultado !== '') {
        $personaDni = Persona::query()
            ->where('dni', $dniConsultado)
            ->first();

        if ($personaDni) {
            $clienteExistente = Cliente::query()
                ->where('persona_id', $personaDni->id)
                ->orWhere('dni', $dniConsultado)
                ->exists();

            $estadoDni = $clienteExistente ? 'cliente-existente' : 'persona-existente';
        } else {
            $estadoDni = 'persona-nueva';
        }
    }

    return view('profesional.agregar-cliente', [
        'dniConsultado' => $dniConsultado,
        'personaDni' => $personaDni,
        'estadoDni' => $estadoDni,
    ]);
});

Route::post('/profesional/clientes/store', function (Request $request) use ($registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    $profesionalSesionId = (int) ($profesional?->id ?? 0);

    if (!$profesionalSesionId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $validated = $request->validate([
        'dni'        => ['required', 'string', 'max:20', 'regex:/^[0-9A-Za-z]{7,20}$/'],
        'nombre'     => ['nullable', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido'   => ['nullable', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'cuil'       => ['nullable', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'correo'     => ['required', 'email', 'max:255', 'unique:credencialesclientes,correo'],
        'fechanac'   => ['nullable', 'date'],
        'telefono'   => ['nullable', 'string', 'max:30', 'regex:/^[0-9]+$/'],
    ]);

    $personaExistente = Persona::query()
        ->where('dni', $validated['dni'])
        ->first();

    if ($personaExistente) {
        $clienteExistente = Cliente::query()
            ->where('persona_id', $personaExistente->id)
            ->orWhere('dni', $validated['dni'])
            ->exists();

        if ($clienteExistente) {
            return back()
                ->withErrors(['dni' => 'El DNI ingresado ya pertenece a un cliente registrado.'])
                ->withInput();
        }
    } else {
        $mensajesError = [];

        if (blank($validated['nombre'] ?? null)) {
            $mensajesError['nombre'] = 'El nombre es obligatorio cuando el DNI no pertenece a una persona registrada.';
        }

        if (blank($validated['apellido'] ?? null)) {
            $mensajesError['apellido'] = 'El apellido es obligatorio cuando el DNI no pertenece a una persona registrada.';
        }

        if (blank($validated['cuil'] ?? null)) {
            $mensajesError['cuil'] = 'El CUIL es obligatorio cuando el DNI no pertenece a una persona registrada.';
        }

        if (blank($validated['fechanac'] ?? null)) {
            $mensajesError['fechanac'] = 'La fecha de nacimiento es obligatoria cuando el DNI no pertenece a una persona registrada.';
        }

        if (blank($validated['telefono'] ?? null)) {
            $mensajesError['telefono'] = 'El telefono es obligatorio cuando el DNI no pertenece a una persona registrada.';
        }

        if (!empty($mensajesError)) {
            return back()
                ->withErrors($mensajesError)
                ->withInput();
        }
    }

    $clienteCreado = null;

    DB::transaction(function () use ($validated, $personaExistente, $profesionalSesionId, &$clienteCreado): void {
        $credencialCliente = \App\Models\CredencialCliente::create([
            'correo' => $validated['correo'],
            'contra' => Hash::make($validated['dni']),
            'token' => null,
            'fecha_hora' => now(),
        ]);

        $persona = $personaExistente;

        if (!$persona) {
            $fotoDefaultId = Foto::query()
                ->where('ruta', 'images/avatar_default.png')
                ->value('id');

            if (!$fotoDefaultId) {
                $fotoDefaultId = Foto::query()->create([
                    'extension' => 'png',
                    'tamanio_bytes' => 0,
                    'nombre' => 'avatar_default',
                    'mime_type' => 'image/png',
                    'ruta' => 'images/avatar_default.png',
                ])->id;
            }

            $persona = Persona::create([
                'dni' => $validated['dni'],
                'nombre' => $validated['nombre'],
                'apellido' => $validated['apellido'],
                'cuil' => $validated['cuil'] ?? null,
                'fechanac' => $validated['fechanac'] ?? null,
                'foto_id' => (int) $fotoDefaultId,
            ]);

            $telefono = Telefono::create([
                'telefono' => $validated['telefono'],
            ]);

            DB::table('personas_telefonos')->insert([
                'dni' => $validated['dni'],
                'persona_id' => $persona->id,
                'telefono_id' => $telefono->id,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }

        $cliente = Cliente::create([
            'dni' => $validated['dni'],
            'correo' => $validated['correo'],
            'credencialcliente_id' => $credencialCliente->id,
            'persona_id' => $persona->id,
            'baja_id' => 1,
        ]);

        $clienteCreado = $cliente;

        DB::table('profesionales_clientes')->insertOrIgnore([
            'profesional_id' => $profesionalSesionId,
            'cliente_id' => $cliente->id,
            'estadorelacion' => 'Activo',
            'created_at' => now(),
            'updated_at' => now(),
        ]);
    });

    if ($clienteCreado) {
        $registrarLogSeguridadProfesional(
            $request,
            'Creación nuevo cliente',
            'El profesional ID ' . $profesionalSesionId . ' creó el cliente ID ' . $clienteCreado->id . ' con DNI ' . $clienteCreado->dni . '.'
        );
    }

    return redirect('/profesional/clientes')->with('success', 'Cliente agregado correctamente.');
});

Route::get('/profesional/clientes/{cliente}/editar', function (Request $request, Cliente $cliente) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesionalSesionId = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->value('id');

    if (!$profesionalSesionId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesionalSesionId)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes editar un cliente no asignado a tu cuenta.']);
    }

    $cliente->load(['persona', 'credencialCliente']);
    $telefonoActual = $cliente->persona?->telefonos()->first();

    return view('profesional.editar-cliente', [
        'cliente' => $cliente,
        'telefonoActual' => $telefonoActual,
    ]);
});

Route::post('/profesional/clientes/{cliente}/actualizar', function (Request $request, Cliente $cliente) use ($registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes editar un cliente no asignado a tu cuenta.']);
    }

    $cliente->load(['persona', 'credencialCliente']);
    $dniAnterior = (string) $cliente->dni;

    $dniRules = [
        'required',
        'string',
        'regex:/^[0-9A-Za-z]{7,20}$/',
        Rule::unique('clientes', 'dni')->ignore((int) $cliente->id),
    ];

    if ($cliente->persona) {
        $dniRules[] = Rule::unique('personas', 'dni')->ignore((int) $cliente->persona->id);
    }

    $validated = $request->validate([
        'dni' => $dniRules,
        'nombre' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'cuil' => ['required', 'string', 'regex:/^[0-9]{11}$/'],
        'fechanac' => ['required', 'date', 'before_or_equal:today'],
        'correo' => [
            'required',
            'email',
            'max:255',
            Rule::unique('clientes', 'correo')->ignore((int) $cliente->id),
            Rule::unique('credencialesclientes', 'correo')->ignore((int) ($cliente->credencialcliente_id ?? 0)),
        ],
        'telefono' => ['required', 'string', 'regex:/^[0-9]{8,15}$/'],
    ]);

    DB::transaction(function () use ($cliente, $validated, $profesional): void {
        if ($cliente->persona) {
            $cliente->persona->update([
                'dni' => $validated['dni'],
                'nombre' => $validated['nombre'],
                'apellido' => $validated['apellido'],
                'cuil' => $validated['cuil'],
                'fechanac' => $validated['fechanac'],
            ]);

            DB::table('personas_telefonos')
                ->where('persona_id', (int) $cliente->persona->id)
                ->update([
                    'dni' => $validated['dni'],
                    'updated_at' => now(),
                ]);

            $telefonoExistente = $cliente->persona->telefonos()->first();

            if ($telefonoExistente) {
                $telefonoExistente->update([
                    'telefono' => $validated['telefono'],
                ]);
            } else {
                $telefonoNuevo = Telefono::create([
                    'telefono' => $validated['telefono'],
                ]);

                DB::table('personas_telefonos')->insertOrIgnore([
                    'dni' => $validated['dni'],
                    'persona_id' => (int) $cliente->persona->id,
                    'telefono_id' => (int) $telefonoNuevo->id,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }
        }

        $cliente->update([
            'dni' => $validated['dni'],
            'correo' => $validated['correo'],
        ]);

        DB::table('profesionales_clientes')
            ->where('profesional_id', (int) $profesional->id)
            ->where('cliente_id', (int) $cliente->id)
            ->update([
                'estadorelacion' => 'Activo',
                'updated_at' => now(),
            ]);

        if ($cliente->credencialCliente) {
            $cliente->credencialCliente->update([
                'correo' => $validated['correo'],
            ]);
        }
    });

    $registrarLogSeguridadProfesional(
        $request,
        'Edición datos cliente',
        'El profesional ID ' . (int) $profesional->id . ' editó el cliente ID ' . (int) $cliente->id . '.'
    );

    if ($dniAnterior !== (string) $validated['dni']) {
        $registrarLogSeguridadProfesional(
            $request,
            'Cambio de DNI Cliente',
            'Cambio de DNI del cliente ID ' . (int) $cliente->id . ': "' . $dniAnterior . '" -> "' . $validated['dni'] . '".'
        );
    }

    return redirect('/profesional/clientes')->with('success', 'Cliente actualizado correctamente.');
});

Route::post('/profesional/clientes/{cliente}/baja', function (Request $request, Cliente $cliente) use ($registrarLogSeguridadProfesional) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes modificar el estado de un cliente no asignado a tu cuenta.']);
    }

    $estabaDeBaja = (int) $cliente->baja_id !== 1;
    $nombreCliente = trim(($cliente->persona?->nombre ?? '') . ' ' . ($cliente->persona?->apellido ?? ''));

    DB::transaction(function () use ($cliente, $profesional): void {
        if ((int) $cliente->baja_id !== 1) {
            $cliente->baja_id = 1;
            $cliente->save();

            DB::table('profesionales_clientes')
                ->where('profesional_id', (int) $profesional->id)
                ->where('cliente_id', (int) $cliente->id)
                ->update([
                    'estadorelacion' => 'Activo',
                    'updated_at' => now(),
                ]);
            return;
        }

        $bajaId = (int) Baja::query()
            ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['baja'])
            ->value('id');

        if ($bajaId <= 0) {
            $bajaId = 2;
        }

        $cliente->baja_id = $bajaId;
        $cliente->save();

        DB::table('profesionales_clientes')
            ->where('profesional_id', (int) $profesional->id)
            ->where('cliente_id', (int) $cliente->id)
            ->update([
                'estadorelacion' => 'Baja',
                'updated_at' => now(),
            ]);
    });

    if ($estabaDeBaja) {
        $registrarLogSeguridadProfesional(
            $request,
            'Dar de alta cliente',
            'El profesional ID ' . (int) $profesional->id . ' dio de alta al cliente ID ' . (int) $cliente->id . ' (' . $nombreCliente . ').'
        );

        $registrarLogSeguridadProfesional(
            $request,
            'dio de alta relacion con cliente',
            'El profesional ID ' . (int) $profesional->id . ' dio de alta la relación con el cliente ID ' . (int) $cliente->id . ' (' . $nombreCliente . ').'
        );

        return redirect('/profesional/clientes')->with('success', 'Cliente reactivado correctamente.');
    }

    $registrarLogSeguridadProfesional(
        $request,
        'Dar de baja cliente',
        'El profesional ID ' . (int) $profesional->id . ' dio de baja al cliente ID ' . (int) $cliente->id . ' (' . $nombreCliente . ').'
    );

    $registrarLogSeguridadProfesional(
        $request,
        'dio de baja relacion con cliente',
        'El profesional ID ' . (int) $profesional->id . ' dio de baja la relación con el cliente ID ' . (int) $cliente->id . ' (' . $nombreCliente . ').'
    );

    return redirect('/profesional/clientes')->with('success', 'Cliente dado de baja correctamente.');
});

Route::get('/profesional/clientes/{cliente}/documentacion', function (Request $request, Cliente $cliente) use ($resolverRutaDocumentacionCliente) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes acceder a la documentación de un cliente no asignado a tu cuenta.']);
    }

    $cliente->load('persona');

    $documentos = DocumentacionCliente::query()
        ->where('cliente_id', (int) $cliente->id)
        ->latest('id')
        ->get()
        ->map(function (DocumentacionCliente $documento) use ($cliente, $resolverRutaDocumentacionCliente) {
            $rutaArchivo = $resolverRutaDocumentacionCliente((int) $cliente->id, (string) $documento->nombre);
            $documento->ruta_relativa = $rutaArchivo ? ('documentacion-clientes/cliente_' . (int) $cliente->id . '/' . $documento->nombre) : null;
            $documento->url_archivo = $rutaArchivo
                ? url('/profesional/clientes/' . $cliente->id . '/documentacion/' . $documento->id . '/ver')
                : null;
            $documento->archivo_existe = $rutaArchivo !== null && File::exists($rutaArchivo);

            $nombreVisible = preg_replace('/^\d{14}_[a-z0-9]{6}_/i', '', (string) $documento->nombre);
            $documento->nombre_visible = str_replace(['-', '_'], ' ', (string) $nombreVisible);

            return $documento;
        });

    return view('profesional.documentacion-cliente', [
        'cliente' => $cliente,
        'documentos' => $documentos,
    ]);
});

Route::post('/profesional/clientes/{cliente}/documentacion', function (Request $request, Cliente $cliente) use ($registrarLogSeguridadProfesional, $directorioDocumentacionPrivada) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes cargar documentación a un cliente no asignado a tu cuenta.']);
    }

    $validated = $request->validate([
        'documentos' => ['required', 'array', 'min:1'],
        'documentos.*' => ['required', 'file', 'mimes:pdf,jpg,jpeg,png,webp,doc,docx,xlsx,txt', 'max:5120'],
    ]);

    $directorio = $directorioDocumentacionPrivada((int) $cliente->id);

    if (!File::isDirectory($directorio)) {
        File::makeDirectory($directorio, 0755, true);
    }

    foreach ($validated['documentos'] as $archivo) {
        $nombreOriginal = (string) $archivo->getClientOriginalName();
        $mimeType = (string) $archivo->getClientMimeType();
        $rutaTemporal = $archivo->getRealPath();
        $tamanioBytes = ($rutaTemporal && is_file($rutaTemporal)) ? (int) filesize($rutaTemporal) : 0;
        $extension = strtolower((string) ($archivo->getClientOriginalExtension() ?: $archivo->extension() ?: 'bin'));
        $nombreBase = Str::slug(pathinfo($nombreOriginal, PATHINFO_FILENAME));

        if ($nombreBase === '') {
            $nombreBase = 'documento';
        }

        $nombreGuardado = now()->format('YmdHis') . '_' . Str::lower(Str::random(6)) . '_' . $nombreBase . '.' . $extension;
        $archivo->move($directorio, $nombreGuardado);

        DocumentacionCliente::create([
            'nombre' => $nombreGuardado,
            'mime_type' => $mimeType,
            'tamanio_bytes' => $tamanioBytes,
            'extension' => $extension,
            'cliente_id' => (int) $cliente->id,
            'profesional_id' => (int) $profesional->id,
        ]);
    }

    $registrarLogSeguridadProfesional(
        $request,
        'Agregó documentación cliente',
        'El profesional ID ' . (int) $profesional->id . ' agregó ' . count($validated['documentos']) . ' documento(s) al cliente ID ' . (int) $cliente->id . '.'
    );

    return redirect('/profesional/clientes/' . $cliente->id . '/documentacion')
        ->with('success', 'Documentación agregada correctamente.');
});

Route::get('/profesional/clientes/{cliente}/documentacion/{documentacion}/ver', function (Request $request, Cliente $cliente, DocumentacionCliente $documentacion) use ($resolverRutaDocumentacionCliente) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional || (int) $documentacion->cliente_id !== (int) $cliente->id) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes visualizar documentación de un cliente no asignado a tu cuenta.']);
    }

    $rutaArchivo = $resolverRutaDocumentacionCliente((int) $cliente->id, (string) $documentacion->nombre);

    if (!$rutaArchivo || !File::exists($rutaArchivo)) {
        return redirect('/profesional/clientes/' . $cliente->id . '/documentacion')
            ->withErrors(['documento' => 'El archivo no se encuentra disponible para visualizar.']);
    }

    $nombreDescarga = $documentacion->nombre_visible ?? preg_replace('/^\d{14}_[a-z0-9]{6}_/i', '', (string) $documentacion->nombre);
    $mimeType = trim((string) ($documentacion->mime_type ?? ''));

    if ($mimeType === '') {
        $mimeType = File::mimeType($rutaArchivo) ?: 'application/octet-stream';
    }

    return response()->file($rutaArchivo, [
        'Content-Type' => $mimeType,
        'Content-Disposition' => 'inline; filename="' . addslashes((string) $nombreDescarga) . '"',
        'X-Content-Type-Options' => 'nosniff',
    ]);
});

Route::get('/profesional/clientes/{cliente}/documentacion/{documentacion}/descargar', function (Request $request, Cliente $cliente, DocumentacionCliente $documentacion) use ($resolverRutaDocumentacionCliente) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional || (int) $documentacion->cliente_id !== (int) $cliente->id) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes descargar documentación de un cliente no asignado a tu cuenta.']);
    }

    $rutaArchivo = $resolverRutaDocumentacionCliente((int) $cliente->id, (string) $documentacion->nombre);

    if (!$rutaArchivo || !File::exists($rutaArchivo)) {
        return redirect('/profesional/clientes/' . $cliente->id . '/documentacion')
            ->withErrors(['documento' => 'El archivo no se encuentra disponible para descargar.']);
    }

    $nombreDescarga = $documentacion->nombre_visible ?? preg_replace('/^\d{14}_[a-z0-9]{6}_/i', '', (string) $documentacion->nombre);

    return response()->download($rutaArchivo, (string) $nombreDescarga, [
        'X-Content-Type-Options' => 'nosniff',
    ]);
});

Route::post('/profesional/clientes/{cliente}/documentacion/{documentacion}/eliminar', function (Request $request, Cliente $cliente, DocumentacionCliente $documentacion) use ($registrarLogSeguridadProfesional, $eliminarDocumentacionCliente) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $clientePerteneceAlProfesional = DB::table('profesionales_clientes')
        ->where('profesional_id', (int) $profesional->id)
        ->where('cliente_id', (int) $cliente->id)
        ->exists();

    if (!$clientePerteneceAlProfesional || (int) $documentacion->cliente_id !== (int) $cliente->id) {
        return redirect('/profesional/clientes')->withErrors(['cliente' => 'No puedes eliminar documentación de un cliente no asignado a tu cuenta.']);
    }

    $eliminarDocumentacionCliente((int) $cliente->id, (string) $documentacion->nombre);

    $nombreDocumento = (string) $documentacion->nombre;
    $documentacion->delete();

    $registrarLogSeguridadProfesional(
        $request,
        'Eliminó documentación cliente',
        'El profesional ID ' . (int) $profesional->id . ' eliminó el documento "' . $nombreDocumento . '" del cliente ID ' . (int) $cliente->id . '.'
    );

    return redirect('/profesional/clientes/' . $cliente->id . '/documentacion')
        ->with('success', 'Documento eliminado correctamente.');
});

Route::get('/profesional/formularios', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
    $profesionalSesionId = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->value('id');

    if (!$profesionalSesionId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $filtroTexto = trim((string) $request->query('q', ''));
    $filtroEstado = trim((string) $request->query('estado', ''));
    $filtroCliente = trim((string) $request->query('cliente', ''));

    $formulariosQuery = Formulario::query()
        ->with(['profesion', 'servicio', 'modalidad'])
        ->whereExists(function ($query) use ($profesionalSesionId): void {
            $query->select(DB::raw(1))
                ->from('profesionales_formularios as pf')
                ->whereColumn('pf.formulario_id', 'formularios.id')
                ->where('pf.profesional_id', (int) $profesionalSesionId);
        });

    if ($filtroTexto !== '') {
        $formulariosQuery->where(function ($query) use ($filtroTexto): void {
            $query->where('formularios.nombrecompleto', 'like', '%' . $filtroTexto . '%')
                ->orWhere('formularios.correo', 'like', '%' . $filtroTexto . '%')
                ->orWhere('formularios.celular', 'like', '%' . $filtroTexto . '%')
                ->orWhere('formularios.descripcion', 'like', '%' . $filtroTexto . '%')
                ->orWhereHas('servicio', function ($qServicio) use ($filtroTexto): void {
                    $qServicio->where('titulo', 'like', '%' . $filtroTexto . '%');
                })
                ->orWhereHas('profesion', function ($qProfesion) use ($filtroTexto): void {
                    $qProfesion->where('titulo', 'like', '%' . $filtroTexto . '%');
                });
        });
    }

    if ($filtroCliente === 'si') {
        $formulariosQuery->whereNotNull('formularios.cliente_id');
    } elseif ($filtroCliente === 'no') {
        $formulariosQuery->whereNull('formularios.cliente_id');
    }

    if ($filtroEstado !== '') {
        if ($filtroEstado === 'aceptado_por_otro') {
            $formulariosQuery
                ->whereRaw("LOWER(TRIM(formularios.estado)) = 'aceptado'")
                ->whereExists(function ($query) use ($profesionalSesionId): void {
                    $query->select(DB::raw(1))
                        ->from('profesionales_formularios as pf')
                        ->whereColumn('pf.formulario_id', 'formularios.id')
                        ->where('pf.profesional_id', '!=', (int) $profesionalSesionId)
                        ->whereRaw("LOWER(TRIM(pf.estado)) = 'aceptado'");
                });
        } elseif ($filtroEstado === 'aceptado') {
            $formulariosQuery
                ->whereRaw("LOWER(TRIM(formularios.estado)) = 'aceptado'")
                ->whereNotExists(function ($query) use ($profesionalSesionId): void {
                    $query->select(DB::raw(1))
                        ->from('profesionales_formularios as pf')
                        ->whereColumn('pf.formulario_id', 'formularios.id')
                        ->where('pf.profesional_id', '!=', (int) $profesionalSesionId)
                        ->whereRaw("LOWER(TRIM(pf.estado)) = 'aceptado'");
                });
        } elseif ($filtroEstado === 'rechazado') {
            $formulariosQuery->whereRaw("LOWER(TRIM(formularios.estado)) = 'rechazado por todos'");
        } else {
            $formulariosQuery->whereRaw('LOWER(TRIM(formularios.estado)) = ?', [mb_strtolower($filtroEstado)]);
        }
    }

    $formularios = $formulariosQuery
        ->orderByRaw('CASE WHEN formularios.cliente_id IS NOT NULL THEN 0 ELSE 1 END')
        ->latest('id')
        ->paginate(10)
        ->withQueryString();

    $formulariosPaginaIds = $formularios->getCollection()->pluck('id')->map(fn ($id) => (int) $id)->all();
    $aceptadosPorOtroIds = [];

    if (!empty($formulariosPaginaIds)) {
        $aceptadosPorOtroIds = DB::table('profesionales_formularios as pf')
            ->join('formularios as f', 'f.id', '=', 'pf.formulario_id')
            ->whereIn('pf.formulario_id', $formulariosPaginaIds)
            ->where('pf.profesional_id', '!=', (int) $profesionalSesionId)
            ->whereRaw("LOWER(TRIM(pf.estado)) = 'aceptado'")
            ->whereRaw("LOWER(TRIM(f.estado)) = 'aceptado'")
            ->pluck('pf.formulario_id')
            ->map(fn ($id) => (int) $id)
            ->all();
    }

    $formularios->getCollection()->transform(function ($formulario) use ($aceptadosPorOtroIds) {
        $formulario->aceptado_por_otro = in_array((int) $formulario->id, $aceptadosPorOtroIds, true);
        return $formulario;
    });

    return view('profesional.revisar-formularios', [
        'formularios' => $formularios,
    ]);
});

Route::get('/profesional/formularios/{formulario}', function (Formulario $formulario) use ($buscarTurnoAutomatico, $formularioFueAceptadoPorOtroProfesional) {
    $formulario->load(['profesion', 'servicio', 'modalidad', 'diasPreferidos', 'horariosPreferidos']);

    $credencialId = (int) request()->session()->get('personal_credencial_id', 0);
    $profesionalSesionId = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->value('id');

    $profesionalPreferido = null;
    if ($formulario->profesional_id) {
        $profesionalPreferido = Profesional::with('persona')->find($formulario->profesional_id);
    }

    $estadoProfesionalFormulario = 'Pendiente';
    $formularioAceptadoPorOtro = false;
    $agendaEventosProfesional = collect();
    $turnoAutomaticoSugerido = null;
    if ($profesionalSesionId) {
        $esAsignado = DB::table('profesionales_formularios')
            ->where('formulario_id', (int) $formulario->id)
            ->where('profesional_id', (int) $profesionalSesionId)
            ->exists();

        if (!$esAsignado) {
            return redirect('/profesional/formularios')
                ->with('profesional_action_error', 'No tenes permiso para ver este formulario.');
        }

        $estadoPivot = DB::table('profesionales_formularios')
            ->where('formulario_id', (int) $formulario->id)
            ->where('profesional_id', (int) $profesionalSesionId)
            ->value('estado');

        if (is_string($estadoPivot) && trim($estadoPivot) !== '') {
            $estadoProfesionalFormulario = trim($estadoPivot);
        }

        $formularioAceptadoPorOtro = $formularioFueAceptadoPorOtroProfesional((int) $formulario->id, (int) $profesionalSesionId);

        $coloresEstado = [
            'confirmado' => '#198754',
            'cancelado' => '#dc3545',
            'reprogramado' => '#fd7e14',
            'cliente ausente' => '#6c757d',
            'cliente presente' => '#0d6efd',
        ];

        $duracionTurnoVisualForm = (int) (Agenda::query()
            ->where('profesional_id', (int) $profesionalSesionId)
            ->value('duracionturno') ?? 30);

        $agendaEventosProfesional = Turno::query()
            ->with(['servicio', 'modalidad', 'estadoTurno', 'cliente.persona.telefonos'])
            ->where('profesional_id', (int) $profesionalSesionId)
            ->orderBy('inicio')
            ->get()
            ->filter(fn ($turno) => $turno->inicio)
            ->filter(function ($turno) {
                $estadoDescripcion = mb_strtolower(trim((string) ($turno->estadoTurno?->descripcion ?? '')));

                return $estadoDescripcion !== 'cancelado';
            })
            ->map(function ($turno) use ($coloresEstado, $duracionTurnoVisualForm) {
                $estadoDescripcion = trim((string) ($turno->estadoTurno?->descripcion ?? 'Sin estado'));
                $estadoKey = mb_strtolower($estadoDescripcion);
                $color = $coloresEstado[$estadoKey] ?? '#0d6efd';

                // Naranja para turnos asignados manualmente (sin cliente vinculado y estado confirmado)
                if ($estadoKey === 'confirmado' && $turno->cliente_id === null) {
                    $color = '#fd7e14';
                }

                $servicioTitulo = trim((string) ($turno->servicio?->titulo ?? 'Turno'));
                $nombreCompleto = trim((string) ($turno->nombrecompleto ?? 'Cliente'));
                $celularCliente = trim((string) ($turno->cliente?->persona?->telefonos?->first()?->telefono ?? ''));
                if ($celularCliente === '') {
                    $celularCliente = trim((string) ($turno->celular ?? ''));
                }

                return [
                    'id' => (int) $turno->id,
                    'title' => $servicioTitulo . ' - ' . $nombreCompleto,
                    'start' => $turno->inicio->format('Y-m-d\TH:i:s'),
                    'end' => $turno->inicio->copy()->addMinutes($duracionTurnoVisualForm)->format('Y-m-d\TH:i:s'),
                    'backgroundColor' => $color,
                    'borderColor' => $color,
                    'extendedProps' => [
                        'prioridad' => 4,
                        'cliente' => $nombreCompleto,
                        'correo' => (string) ($turno->correo ?? ''),
                        'celular' => $celularCliente !== '' ? $celularCliente : '-',
                        'servicio' => $servicioTitulo,
                        'estado' => $estadoDescripcion,
                        'modalidad' => (string) ($turno->modalidad?->descripcion ?? '-'),
                    ],
                ];
            })
            ->values();

        // Eventos de disponibilidad (verde) y no disponibilidad (rojo)
        $numeroDiaSemanaForm = [
            'domingo' => 0, 'lunes' => 1, 'martes' => 2,
            'miercoles' => 3, 'miércoles' => 3, 'jueves' => 4,
            'viernes' => 5, 'sabado' => 6, 'sábado' => 6,
        ];

        $agendaForm = Agenda::query()
            ->with(['diaDeAtencion.dias', 'diaDeAtencion.horariosAtenciones', 'diaDeAtencion.horariosRecesos', 'feriado', 'modoVacaciones'])
            ->where('profesional_id', (int) $profesionalSesionId)
            ->first();

        $inicioRangoForm = now()->startOfDay();
        $finRangoForm    = now()->addDays(90)->endOfDay();

        $eventosDispForm = collect($agendaForm?->diaDeAtencion ?? [])
            ->flatMap(function ($diaAtencion) use ($numeroDiaSemanaForm, $inicioRangoForm, $finRangoForm) {
                $diaDesc = mb_strtolower(trim((string) ($diaAtencion->dias?->descripcion ?? '')));
                $diaSem  = $numeroDiaSemanaForm[$diaDesc] ?? null;
                if ($diaSem === null) {
                    return [];
                }
                return collect($diaAtencion->horariosAtenciones ?? [])
                    ->filter(fn ($h) => $h->horariocomienzo && $h->horariofin)
                    ->flatMap(function ($horario) use ($diaSem, $diaAtencion, $inicioRangoForm, $finRangoForm) {
                        $eventos = [];
                        $cursor  = $inicioRangoForm->copy();
                        while ($cursor->lte($finRangoForm)) {
                            if ((int) $cursor->dayOfWeek === (int) $diaSem) {
                                $fecha = $cursor->format('Y-m-d');
                                $eventos[] = [
                                    'id'              => 'disp-f-' . $diaAtencion->id . '-' . $horario->id . '-' . $fecha,
                                    'title'           => 'Disponibilidad',
                                    'start'           => $fecha . 'T' . substr((string) $horario->horariocomienzo, 0, 8),
                                    'end'             => $fecha . 'T' . substr((string) $horario->horariofin, 0, 8),
                                    'display'         => 'background',
                                    'classNames'      => ['evento-disponibilidad'],
                                    'backgroundColor' => '#198754',
                                    'borderColor'     => '#198754',
                                    'extendedProps'   => ['prioridad' => 5, 'tipoEvento' => 'disponibilidad', 'estado' => 'Disponible', 'modalidad' => '-'],
                                ];
                            }
                            $cursor->addDay();
                        }
                        return $eventos;
                    });
            })
            ->values();

        $eventosRecesoForm = collect($agendaForm?->diaDeAtencion ?? [])
            ->flatMap(function ($diaAtencion) use ($numeroDiaSemanaForm, $inicioRangoForm, $finRangoForm) {
                $diaDesc = mb_strtolower(trim((string) ($diaAtencion->dias?->descripcion ?? '')));
                $diaSem  = $numeroDiaSemanaForm[$diaDesc] ?? null;
                if ($diaSem === null) {
                    return [];
                }
                return collect($diaAtencion->horariosRecesos ?? [])
                    ->filter(fn ($r) => $r->comienzo && $r->fin)
                    ->flatMap(function ($receso) use ($diaSem, $diaAtencion, $inicioRangoForm, $finRangoForm) {
                        $eventos = [];
                        $cursor  = $inicioRangoForm->copy();
                        while ($cursor->lte($finRangoForm)) {
                            if ((int) $cursor->dayOfWeek === (int) $diaSem) {
                                $fecha = $cursor->format('Y-m-d');
                                $descripcionReceso = trim((string) ($receso->descripcion ?? ''));
                                $tituloReceso = $descripcionReceso !== ''
                                    ? 'Receso: ' . $descripcionReceso
                                    : 'Receso';

                                $eventos[] = [
                                    'id'              => 'receso-f-' . $diaAtencion->id . '-' . $receso->id . '-' . $fecha,
                                    'title'           => $tituloReceso,
                                    'start'           => $fecha . 'T' . substr((string) $receso->comienzo, 0, 8),
                                    'end'             => $fecha . 'T' . substr((string) $receso->fin, 0, 8),
                                    'display'         => 'background',
                                    'classNames'      => ['evento-receso'],
                                    'textColor'       => '#5b1f23',
                                    'backgroundColor' => '#dc3545',
                                    'borderColor'     => '#dc3545',
                                    'extendedProps'   => ['prioridad' => 3, 'tipoEvento' => 'no_disponible', 'motivo' => 'Receso', 'estado' => 'No disponible'],
                                ];
                            }
                            $cursor->addDay();
                        }
                        return $eventos;
                    });
            })
            ->values();

        $eventosVacacionesForm = collect($agendaForm?->modoVacaciones ?? [])
            ->filter(fn ($v) => $v->inicio && $v->fin)
            ->map(function ($vacacion) {
                $descripcionVacacion = trim((string) ($vacacion->descripcion ?? ''));

                return [
                    'id'              => 'vacaciones-f-' . $vacacion->id,
                    'title'           => $descripcionVacacion !== ''
                        ? 'Vacaciones: ' . $descripcionVacacion
                        : 'Vacaciones',
                    'start'           => (string) $vacacion->inicio,
                    'end'             => \Illuminate\Support\Carbon::parse((string) $vacacion->fin)->addDay()->format('Y-m-d'),
                    'allDay'          => true,
                    'display'         => 'background',
                    'classNames'      => ['evento-vacaciones'],
                    'textColor'       => '#5b1f23',
                    'backgroundColor' => '#dc3545',
                    'borderColor'     => '#dc3545',
                    'extendedProps'   => ['prioridad' => 2, 'tipoEvento' => 'no_disponible', 'motivo' => 'Vacaciones', 'estado' => 'No disponible'],
                ];
            })
            ->values();

        $eventosFeriadosForm = collect($agendaForm?->feriado ?? [])
            ->filter(fn ($f) => $f->fecha)
            ->map(function ($feriado) {
                $descripcionFeriado = trim((string) ($feriado->descripcion ?? ''));

                return [
                    'id'              => 'feriado-f-' . $feriado->id,
                    'title'           => $descripcionFeriado !== ''
                        ? 'Feriado: ' . $descripcionFeriado
                        : 'Feriado',
                    'start'           => (string) $feriado->fecha,
                    'end'             => \Illuminate\Support\Carbon::parse((string) $feriado->fecha)->addDay()->format('Y-m-d'),
                    'allDay'          => true,
                    'display'         => 'background',
                    'classNames'      => ['evento-feriado'],
                    'textColor'       => '#5b1f23',
                    'backgroundColor' => '#dc3545',
                    'borderColor'     => '#dc3545',
                    'extendedProps'   => ['prioridad' => 1, 'tipoEvento' => 'no_disponible', 'motivo' => 'Feriado', 'estado' => 'No disponible'],
                ];
            })
            ->values();

        $duracionTurnoForm = (int) ($agendaForm?->duracionturno ?? 30);

        $rangosTurnosOcupadosForm = Turno::query()
            ->with('estadoTurno')
            ->where('profesional_id', (int) $profesionalSesionId)
            ->get()
            ->filter(fn ($turno) => $turno->inicio)
            ->filter(function ($turno) {
                $estadoDescripcion = mb_strtolower(trim((string) ($turno->estadoTurno?->descripcion ?? '')));

                return $estadoDescripcion !== 'cancelado';
            })
            ->map(function ($turno) use ($duracionTurnoForm) {
                $inicio = \Illuminate\Support\Carbon::parse((string) $turno->inicio);

                return [
                    'inicio' => $inicio->copy(),
                    'fin' => $inicio->copy()->addMinutes($duracionTurnoForm),
                ];
            })
            ->values();

        $rangosNoDispo = collect()
            ->concat($rangosTurnosOcupadosForm)
            ->concat($eventosRecesoForm)
            ->concat($eventosVacacionesForm)
            ->concat($eventosFeriadosForm)
            ->map(function ($ev) {
                $ini = isset($ev['start']) ? \Illuminate\Support\Carbon::parse((string) $ev['start']) : null;
                $fin = isset($ev['end']) ? \Illuminate\Support\Carbon::parse((string) $ev['end']) : null;

                if (isset($ev['inicio'], $ev['fin'])) {
                    return ['inicio' => $ev['inicio']->copy(), 'fin' => $ev['fin']->copy()];
                }

                return ($ini && $fin) ? ['inicio' => $ini, 'fin' => $fin] : null;
            })
            ->filter()
            ->values();

        $eventosDispForm = $eventosDispForm
            ->flatMap(function ($evento) use ($rangosNoDispo) {
                if (!isset($evento['start'], $evento['end'])) {
                    return [$evento];
                }
                $iniEv = \Illuminate\Support\Carbon::parse((string) $evento['start']);
                $finEv = \Illuminate\Support\Carbon::parse((string) $evento['end']);
                $solapados = $rangosNoDispo
                    ->filter(fn ($r) => $iniEv->lt($r['fin']) && $finEv->gt($r['inicio']))
                    ->map(fn ($r) => [
                        'inicio' => $r['inicio']->gt($iniEv) ? $r['inicio']->copy() : $iniEv->copy(),
                        'fin'    => $r['fin']->lt($finEv) ? $r['fin']->copy() : $finEv->copy(),
                    ])
                    ->values()
                    ->all();
                if (empty($solapados)) {
                    return [$evento];
                }
                usort($solapados, fn ($a, $b) => $a['inicio']->lessThan($b['inicio']) ? -1 : ($a['inicio']->equalTo($b['inicio']) ? 0 : 1));
                $segmentos = [['inicio' => $iniEv->copy(), 'fin' => $finEv->copy()]];
                foreach ($solapados as $rango) {
                    $nuevos = [];
                    foreach ($segmentos as $seg) {
                        if ($seg['fin']->lessThanOrEqualTo($rango['inicio']) || $seg['inicio']->greaterThanOrEqualTo($rango['fin'])) {
                            $nuevos[] = $seg;
                            continue;
                        }
                        if ($seg['inicio']->lt($rango['inicio'])) {
                            $nuevos[] = ['inicio' => $seg['inicio']->copy(), 'fin' => $rango['inicio']->copy()];
                        }
                        if ($seg['fin']->gt($rango['fin'])) {
                            $nuevos[] = ['inicio' => $rango['fin']->copy(), 'fin' => $seg['fin']->copy()];
                        }
                    }
                    $segmentos = $nuevos;
                }
                return collect($segmentos)
                    ->filter(fn ($s) => $s['inicio']->lt($s['fin']))
                    ->values()
                    ->map(function ($s, $idx) use ($evento) {
                        $ev = $evento;
                        $ev['id']    = (string) ($evento['id'] ?? 'disp') . '-seg-' . $idx;
                        $ev['start'] = $s['inicio']->format('Y-m-d\TH:i:s');
                        $ev['end']   = $s['fin']->format('Y-m-d\TH:i:s');
                        return $ev;
                    })
                    ->all();
            })
            ->values();

        $agendaEventosProfesional = collect()
            ->concat($eventosFeriadosForm)
            ->concat($eventosVacacionesForm)
            ->concat($eventosRecesoForm)
            ->concat($agendaEventosProfesional)
            ->concat($eventosDispForm)
            ->values();

        if (!$formularioAceptadoPorOtro) {
            $turnoAutomaticoSugerido = $buscarTurnoAutomatico($formulario, (int) $profesionalSesionId);
        }
    }

    return view('profesional.detalle-formulario', [
        'formulario' => $formulario,
        'profesionalPreferido' => $profesionalPreferido,
        'estadoProfesionalFormulario' => $estadoProfesionalFormulario,
        'formularioAceptadoPorOtro' => $formularioAceptadoPorOtro,
        'agendaEventosProfesional' => $agendaEventosProfesional,
        'turnoAutomaticoSugerido' => $turnoAutomaticoSugerido,
    ]);
});

Route::post('/profesional/formularios/{formulario}/rechazar', function (Request $request, Formulario $formulario) use ($formularioFueAceptadoPorOtroProfesional, $registrarLogSeguridadProfesional, $recalcularEstadoGeneralFormulario) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    $profesionalId = $profesional?->id;

    if (!$profesionalId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $estadoRelacionAnterior = mb_strtolower(trim((string) DB::table('profesionales_formularios')
        ->where('profesional_id', (int) $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->value('estado')));

    $esAsignado = DB::table('profesionales_formularios')
        ->where('profesional_id', (int) $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->exists();

    if (!$esAsignado) {
        return redirect('/profesional/formularios')
            ->with('profesional_action_error', 'No tenes permiso para rechazar este formulario.');
    }

    if ($formularioFueAceptadoPorOtroProfesional((int) $formulario->id, (int) $profesionalId)) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'Este formulario ya fue aceptado por otro profesional.');
    }

    $estadoNuevoProfesional = $estadoRelacionAnterior === 'aceptado' ? 'Pendiente' : 'Rechazado';

    DB::table('profesionales_formularios')
        ->where('profesional_id', (int) $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->update([
            'estado' => $estadoNuevoProfesional,
        ]);

    $recalcularEstadoGeneralFormulario($formulario);

    $registrarLogSeguridadProfesional(
        $request,
        $estadoRelacionAnterior === 'aceptado' ? 'Devolvió un caso' : 'Rechazó un caso',
        $estadoRelacionAnterior === 'aceptado'
            ? 'El profesional ID ' . $profesionalId . ' devolvió el formulario ID ' . $formulario->id . '.'
            : 'El profesional ID ' . $profesionalId . ' rechazó el formulario ID ' . $formulario->id . '.'
    );

    return redirect('/profesional/formularios/' . $formulario->id)
        ->with('profesional_action_success', $estadoRelacionAnterior === 'aceptado'
            ? 'Formulario devuelto correctamente. Ahora quedó nuevamente pendiente.'
            : 'Formulario rechazado correctamente.');
});

Route::post('/profesional/formularios/{formulario}/asignar-turno-manual', function (Request $request, Formulario $formulario) use ($obtenerAdvertenciasTurno, $verificarConflictoTurno, $formularioFueAceptadoPorOtroProfesional, $enviarCorreoTurno, $registrarLogSeguridadProfesional, $recalcularEstadoGeneralFormulario) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    $profesionalId = (int) ($profesional?->id ?? 0);

    if (!$profesionalId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $esAsignado = DB::table('profesionales_formularios')
        ->where('profesional_id', $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->exists();

    if (!$esAsignado) {
        return redirect('/profesional/formularios')
            ->with('profesional_action_error', 'No tenes permiso para asignar turno en este formulario.');
    }

    if ($formularioFueAceptadoPorOtroProfesional((int) $formulario->id, $profesionalId)) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'Este formulario ya fue aceptado por otro profesional.');
    }

    $validated = $request->validate([
        'fecha_turno' => ['required', 'date', 'after_or_equal:today'],
        'hora_turno' => ['required', 'date_format:H:i'],
        'omitir_restricciones' => ['nullable', 'boolean'],
    ]);

    $inicio = \Illuminate\Support\Carbon::parse($validated['fecha_turno'] . ' ' . $validated['hora_turno'] . ':00');

    $agenda = Agenda::query()->firstOrCreate(
        ['profesional_id' => $profesionalId],
        [
            'estado' => 'Activa',
            'duracionturno' => 30,
        ]
    );

    $duracion = (int) ($agenda->duracionturno ?? 30);

    $advertencias = $obtenerAdvertenciasTurno((int) $agenda->id, $duracion, $inicio);
    $errorDisponibilidad = $verificarConflictoTurno((int) $agenda->id, $duracion, $inicio);

    if ($errorDisponibilidad !== null) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'Ese dia y horario no se encuentra disponible: ' . $errorDisponibilidad)
            ->withInput();
    }

    if (!empty($advertencias) && !((bool) ($validated['omitir_restricciones'] ?? false))) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'El turno elegido coincide con restricciones de agenda. Revisá las advertencias para decidir si querés asignarlo igualmente.')
            ->with('formulario_turno_manual_warning', [
                'advertencias' => $advertencias,
                'inputs' => [
                    'fecha_turno' => $validated['fecha_turno'],
                    'hora_turno' => $validated['hora_turno'],
                ],
            ])
            ->withInput();
    }

    $estadoConfirmadoId = EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['confirmado'])
        ->value('id') ?? 1;

    Turno::create([
        'inicio' => $inicio,
        'correo' => (string) ($formulario->correo ?? ''),
        'celular' => (string) ($formulario->celular ?? ''),
        'nombrecompleto' => (string) ($formulario->nombrecompleto ?? 'Sin nombre'),
        'descripcion' => (string) ($formulario->descripcion ?? 'Turno asignado manualmente.'),
        'cliente_id' => $formulario->cliente_id ? (int) $formulario->cliente_id : null,
        'estadoturno_id' => (int) $estadoConfirmadoId,
        'agenda_id' => (int) $agenda->id,
        'profesional_id' => $profesionalId,
        'servicio_id' => (int) $formulario->servicio_id,
        'modalidad_id' => (int) $formulario->modalidad_id,
    ]);

    $profesional->loadMissing('persona');
    $nombreProfesional = trim((string) (($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? '')));
    $servicioTitulo = (string) (Servicio::query()->where('id', (int) $formulario->servicio_id)->value('titulo') ?? 'Servicio');
    $modalidadDescripcion = (string) (Modalidad::query()->where('id', (int) $formulario->modalidad_id)->value('descripcion') ?? 'Modalidad');

    $enviarCorreoTurno(
        'asignacion',
        (string) ($formulario->correo ?? ''),
        (string) ($formulario->nombrecompleto ?? ''),
        $inicio->copy(),
        $servicioTitulo,
        $modalidadDescripcion,
        $nombreProfesional
    );

    DB::table('profesionales_formularios')
        ->where('profesional_id', $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->update([
            'estado' => 'Aceptado',
        ]);

    $recalcularEstadoGeneralFormulario($formulario);

    $registrarLogSeguridadProfesional(
        $request,
        'Aceptó un caso',
        'El profesional ID ' . $profesionalId . ' aceptó el formulario ID ' . $formulario->id . '.'
    );

    $registrarLogSeguridadProfesional(
        $request,
        'Asignó un turno',
        'El profesional ID ' . $profesionalId . ' asignó manualmente un turno desde el formulario ID ' . $formulario->id . ' para el ' . $inicio->format('d/m/Y H:i') . '.'
    );

    return redirect('/profesional/formularios/' . $formulario->id)
        ->with('profesional_action_success', 'Turno asignado correctamente.');
});

Route::post('/profesional/formularios/{formulario}/asignar-turno-automatico', function (Request $request, Formulario $formulario) use ($buscarTurnoAutomatico, $verificarDisponibilidadTurno, $formularioFueAceptadoPorOtroProfesional, $enviarCorreoTurno, $registrarLogSeguridadProfesional, $recalcularEstadoGeneralFormulario) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    $profesionalId = (int) ($profesional?->id ?? 0);

    if (!$profesionalId) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $esAsignado = DB::table('profesionales_formularios')
        ->where('profesional_id', $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->exists();

    if (!$esAsignado) {
        return redirect('/profesional/formularios')
            ->with('profesional_action_error', 'No tenes permiso para asignar turno en este formulario.');
    }

    if ($formularioFueAceptadoPorOtroProfesional((int) $formulario->id, $profesionalId)) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'Este formulario ya fue aceptado por otro profesional.');
    }

    $formulario->load(['diasPreferidos', 'horariosPreferidos']);
    $sugerencia = $buscarTurnoAutomatico($formulario, $profesionalId);

    if (!$sugerencia || !isset($sugerencia['inicio'], $sugerencia['agenda_id'], $sugerencia['duracion'])) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'No se encontró un turno automático disponible desde mañana.');
    }

    $inicio = $sugerencia['inicio']->copy();
    $inicioMinimo = now()->addDay()->startOfDay();

    if ($inicio->lt($inicioMinimo)) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'El turno automático debe ser a partir del día siguiente.');
    }

    $errorDisponibilidad = $verificarDisponibilidadTurno((int) $sugerencia['agenda_id'], (int) $sugerencia['duracion'], $inicio->copy());
    if ($errorDisponibilidad !== null) {
        return redirect('/profesional/formularios/' . $formulario->id)
            ->with('profesional_action_error', 'No se pudo asignar automáticamente el turno: ' . $errorDisponibilidad);
    }

    $estadoConfirmadoId = EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['confirmado'])
        ->value('id') ?? 1;

    Turno::create([
        'inicio' => $inicio,
        'correo' => (string) ($formulario->correo ?? ''),
        'celular' => (string) ($formulario->celular ?? ''),
        'nombrecompleto' => (string) ($formulario->nombrecompleto ?? 'Sin nombre'),
        'descripcion' => (string) ($formulario->descripcion ?? 'Turno asignado automáticamente.'),
        'cliente_id' => $formulario->cliente_id ? (int) $formulario->cliente_id : null,
        'estadoturno_id' => (int) $estadoConfirmadoId,
        'agenda_id' => (int) $sugerencia['agenda_id'],
        'profesional_id' => $profesionalId,
        'servicio_id' => (int) $formulario->servicio_id,
        'modalidad_id' => (int) $formulario->modalidad_id,
    ]);

    $profesional->loadMissing('persona');
    $nombreProfesional = trim((string) (($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? '')));
    $servicioTitulo = (string) (Servicio::query()->where('id', (int) $formulario->servicio_id)->value('titulo') ?? 'Servicio');
    $modalidadDescripcion = (string) (Modalidad::query()->where('id', (int) $formulario->modalidad_id)->value('descripcion') ?? 'Modalidad');

    $enviarCorreoTurno(
        'asignacion',
        (string) ($formulario->correo ?? ''),
        (string) ($formulario->nombrecompleto ?? ''),
        $inicio->copy(),
        $servicioTitulo,
        $modalidadDescripcion,
        $nombreProfesional
    );

    DB::table('profesionales_formularios')
        ->where('profesional_id', $profesionalId)
        ->where('formulario_id', (int) $formulario->id)
        ->update([
            'estado' => 'Aceptado',
        ]);

    $recalcularEstadoGeneralFormulario($formulario);

    $registrarLogSeguridadProfesional(
        $request,
        'Aceptó un caso',
        'El profesional ID ' . $profesionalId . ' aceptó el formulario ID ' . $formulario->id . '.'
    );

    $registrarLogSeguridadProfesional(
        $request,
        'Asignó un turno',
        'El profesional ID ' . $profesionalId . ' asignó automáticamente un turno desde el formulario ID ' . $formulario->id . ' para el ' . $inicio->format('d/m/Y H:i') . '.'
    );

    return redirect('/profesional/formularios/' . $formulario->id)
        ->with('profesional_action_success', 'Turno automático asignado para el ' . $inicio->format('d/m/Y') . ' a las ' . $inicio->format('H:i') . '.');
});

Route::get('/profesional/notificaciones', function (Request $request) {
    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $profesional = Profesional::query()
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$profesional) {
        return redirect('/login/personal')->with('login_error', 'Debes iniciar sesión como profesional.');
    }

    $profesionalId = (int) $profesional->id;
    $notificaciones = [];

    // 1. Turnos de hoy
    $hoy = now()->toDateString();
    $turnosHoy = Turno::with(['servicio', 'estadoTurno'])
        ->where('profesional_id', $profesionalId)
        ->whereDate('inicio', $hoy)
        ->orderBy('inicio')
        ->get();

    foreach ($turnosHoy as $turno) {
        $nombre   = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
        $hora     = $turno->inicio ? $turno->inicio->format('H:i') : '-';
        $servicio = trim((string) ($turno->servicio?->titulo ?? 'Turno'));
        $estado   = trim((string) ($turno->estadoTurno?->descripcion ?? ''));
        $notificaciones[] = [
            'tipo'        => 'turno_hoy',
            'titulo'      => 'Turno hoy a las ' . $hora . ' — ' . $nombre,
            'descripcion' => $servicio . ($estado !== '' ? ' · Estado: ' . $estado : ''),
            'fecha'       => 'Hoy ' . $hora,
            'clave'       => base64_encode('turno_hoy|' . ('Turno hoy a las ' . $hora . ' — ' . $nombre) . '|' . ('Hoy ' . $hora)),
            'enlace'      => '',
        ];
    }

    // 2. Turnos de mañana
    $manana = now()->addDay()->toDateString();
    $turnosManana = Turno::with(['servicio', 'estadoTurno'])
        ->where('profesional_id', $profesionalId)
        ->whereDate('inicio', $manana)
        ->orderBy('inicio')
        ->get();

    foreach ($turnosManana as $turno) {
        $nombre   = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
        $hora     = $turno->inicio ? $turno->inicio->format('H:i') : '-';
        $servicio = trim((string) ($turno->servicio?->titulo ?? 'Turno'));
        $estado   = trim((string) ($turno->estadoTurno?->descripcion ?? ''));
        $notificaciones[] = [
            'tipo'        => 'turno_manana',
            'titulo'      => 'Turno mañana a las ' . $hora . ' — ' . $nombre,
            'descripcion' => $servicio . ($estado !== '' ? ' · Estado: ' . $estado : ''),
            'fecha'       => 'Mañana ' . $hora,
            'clave'       => base64_encode('turno_manana|' . ('Turno mañana a las ' . $hora . ' — ' . $nombre) . '|' . ('Mañana ' . $hora)),
            'enlace'      => '',
        ];
    }

    // 3. Turnos cancelados recientes (últimos 7 días)
    $estadoCanceladoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])
        ->value('id') ?? 0);

    if ($estadoCanceladoId > 0) {
        $turnosCancelados = Turno::with(['servicio'])
            ->where('profesional_id', $profesionalId)
            ->where('estadoturno_id', $estadoCanceladoId)
            ->where('updated_at', '>=', now()->subDays(7))
            ->orderByDesc('updated_at')
            ->get();

        foreach ($turnosCancelados as $turno) {
            $nombre   = trim((string) ($turno->nombrecompleto ?? 'Sin nombre'));
            $fecha    = $turno->inicio ? $turno->inicio->format('d/m/Y H:i') : '-';
            $servicio = trim((string) ($turno->servicio?->titulo ?? 'Turno'));
            $notificaciones[] = [
                'tipo'        => 'turno_cancelado',
                'titulo'      => 'Turno cancelado — ' . $nombre,
                'descripcion' => $servicio . ' · Fecha del turno: ' . $fecha,
                'fecha'       => $turno->updated_at ? $turno->updated_at->format('d/m/Y') : '-',
                'clave'       => base64_encode('turno_cancelado|' . ('Turno cancelado — ' . $nombre) . '|' . ($turno->updated_at ? $turno->updated_at->format('d/m/Y') : '-')),
                'enlace'      => '',
            ];
        }
    }

    return view('profesional.notificaciones', [
        'notificaciones' => $notificaciones,
    ]);
});

Route::get('/administrador/dashboard', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $quienesSomos = $contenidoWeb?->quienessomos;

    $profesionales = Profesional::with(['persona.Foto', 'profesion', 'profesiones'])
        ->where('baja_id', 1)
        ->get();

    $servicios = Servicio::with('foto')
        ->where('estado', 'activo')
        ->where('visibleenweb', 'si')
        ->get();

    $profesiones = Profesion::query()
        ->where('visible_en_formulario', true)
        ->orderBy('titulo')
        ->get();

    $modalidades = Modalidad::query()
        ->orderBy('descripcion')
        ->get();

    return view('administrador.dashboard', [
        'quienesSomos'  => $quienesSomos,
        'profesionales' => $profesionales,
        'servicios'     => $servicios,
        'profesiones'   => $profesiones,
        'modalidades'   => $modalidades,
    ]);
});

Route::get('/administrador/perfil', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $administrador = Administrador::with(['persona.telefonos', 'credencial'])
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$administrador) {
        return redirect('/administrador/dashboard')
            ->with('admin_action_error', 'No se encontró el perfil de administradora asociado a la sesión actual.');
    }

    return view('administrador.perfil', [
        'administrador' => $administrador,
        'telefonoActual' => $administrador->persona?->telefonos?->first(),
    ]);
});

Route::put('/administrador/perfil', function (Request $request) use ($validarSesionAdmin, $registrarLogSeguridad) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $administrador = Administrador::with(['persona.telefonos', 'credencial'])
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$administrador) {
        return redirect('/administrador/dashboard')
            ->with('admin_action_error', 'No se encontró el perfil de administradora asociado a la sesión actual.');
    }

    $validated = $request->validate([
        'nombre' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'correo' => ['required', 'email', 'max:255'],
        'telefono' => ['nullable', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'usuario' => ['required', 'string', 'max:100', 'unique:credencialesprofesionales,usuario,' . $administrador->credencialprofesional_id],
        'contra_actual' => ['required', 'string'],
        'contra' => ['nullable', 'string', 'min:6', 'confirmed'],
    ]);

    $nombreAnterior = (string) ($administrador->persona?->nombre ?? '');
    $apellidoAnterior = (string) ($administrador->persona?->apellido ?? '');
    $correoAnterior = (string) ($administrador->correo ?? '');
    $telefonoAnterior = (string) ($administrador->persona?->telefonos?->first()?->telefono ?? '');
    $usuarioAnterior = (string) ($administrador->credencial?->usuario ?? '');

    $contraActualIngresada = (string) ($validated['contra_actual'] ?? '');
    $contraActualGuardada = (string) ($administrador->credencial?->contra ?? '');

    $contraActualValida = $contraActualIngresada !== ''
        && ($contraActualIngresada === $contraActualGuardada || Hash::check($contraActualIngresada, $contraActualGuardada));

    if (!$contraActualValida) {
        $registrarLogSeguridad(
            $request,
            'Cambio de contraseña frustrado',
            'La administradora ID ' . (int) $administrador->id . ' ingresó una contraseña actual inválida al intentar modificar sus credenciales.'
        );

        return back()
            ->withErrors(['contra_actual' => 'La contraseña actual no es correcta.'])
            ->withInput($request->except(['contra_actual', 'contra', 'contra_confirmation']));
    }

    DB::transaction(function () use ($administrador, $validated): void {
        $administrador->persona?->update([
            'nombre' => $validated['nombre'],
            'apellido' => $validated['apellido'],
        ]);

        if ($administrador->persona && !blank($validated['telefono'] ?? null)) {
            $telefonoExistente = $administrador->persona->telefonos()->first();

            if ($telefonoExistente) {
                $telefonoExistente->update([
                    'telefono' => $validated['telefono'],
                ]);
            } else {
                $telefonoNuevo = Telefono::create([
                    'telefono' => $validated['telefono'],
                ]);

                DB::table('personas_telefonos')->insertOrIgnore([
                    'dni' => (string) $administrador->dni,
                    'persona_id' => (int) $administrador->persona->id,
                    'telefono_id' => (int) $telefonoNuevo->id,
                    'created_at' => now(),
                    'updated_at' => now(),
                ]);
            }
        }

        $administrador->update([
            'correo' => $validated['correo'],
        ]);

        $datosCredencial = [
            'usuario' => $validated['usuario'],
        ];

        if (!empty($validated['contra'])) {
            $datosCredencial['contra'] = Hash::make($validated['contra']);
        }

        $administrador->credencial?->update($datosCredencial);
    });

    $nombreCompleto = trim($validated['nombre'] . ' ' . $validated['apellido']);
    $request->session()->put([
        'personal_nombre' => $nombreCompleto,
        'personal_apellido' => $validated['apellido'],
        'personal_usuario' => $validated['usuario'],
    ]);

    $registrarLogSeguridad(
        $request,
        'Edito los datos del administrador',
        'La administradora ID ' . (int) $administrador->id
            . ' actualizó su perfil. Nombre: "' . $nombreAnterior . '" -> "' . $validated['nombre']
            . '", Apellido: "' . $apellidoAnterior . '" -> "' . $validated['apellido']
            . '", Correo: "' . $correoAnterior . '" -> "' . $validated['correo']
            . '", Teléfono: "' . $telefonoAnterior . '" -> "' . ($validated['telefono'] ?? '')
            . '", Usuario: "' . $usuarioAnterior . '" -> "' . $validated['usuario'] . '".'
    );

    if (!empty($validated['contra'])) {
        $registrarLogSeguridad(
            $request,
            'Cambio de contraseña exitoso',
            'La administradora ID ' . (int) $administrador->id . ' cambió su contraseña desde su perfil.'
        );
    }

    return redirect('/administrador/perfil')
        ->with('admin_action_success', 'Datos de administradora actualizados correctamente.');
});

Route::post('/administrador/perfil/pregunta-secreta', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $credencialId = (int) $request->session()->get('personal_credencial_id', 0);

    $administrador = Administrador::with('credencial')
        ->where('credencialprofesional_id', $credencialId)
        ->first();

    if (!$administrador) {
        return redirect('/administrador/dashboard')
            ->with('admin_action_error', 'No se encontró el perfil de administradora asociado a la sesión actual.');
    }

    $validated = $request->validate([
        'contra_actual_secreta' => ['required', 'string'],
        'pregunta_secreta' => ['required', 'string', 'max:255'],
        'respuesta_secreta' => ['required', 'string', 'max:255'],
    ]);

    $contraActualIngresada = (string) ($validated['contra_actual_secreta'] ?? '');
    $contraActualGuardada = (string) ($administrador->credencial?->contra ?? '');

    $contraActualValida = $contraActualIngresada !== ''
        && ($contraActualIngresada === $contraActualGuardada || Hash::check($contraActualIngresada, $contraActualGuardada));

    if (!$contraActualValida) {
        return back()
            ->withErrors(['contra_actual_secreta' => 'La contraseña actual no es correcta.'])
            ->withInput($request->except(['contra_actual_secreta', 'respuesta_secreta']));
    }

    $administrador->update([
        'pregunta_secreta_hash' => mb_strtolower(trim((string) $validated['pregunta_secreta'])),
        'respuesta_secreta_hash' => Hash::make(mb_strtolower(trim((string) $validated['respuesta_secreta']))),
    ]);

    return redirect('/administrador/perfil')
        ->with('admin_action_success', 'Pregunta y respuesta secreta actualizadas correctamente.');
});

Route::get('/administrador/logs', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $query = LogSeguridad::query();

    $filtroAccion = trim((string) $request->query('accion', ''));
    $filtroDesde = trim((string) $request->query('desde', ''));
    $filtroHasta = trim((string) $request->query('hasta', ''));
    $filtroTexto = trim((string) $request->query('q', ''));

    if ($filtroAccion !== '') {
        $query->where('accion_descripcion', $filtroAccion);
    }

    if ($filtroDesde !== '') {
        $query->whereDate('fechahora', '>=', $filtroDesde);
    }

    if ($filtroHasta !== '') {
        $query->whereDate('fechahora', '<=', $filtroHasta);
    }

    if ($filtroTexto !== '') {
        $query->where(function ($q) use ($filtroTexto): void {
            $q->where('descripcion', 'like', '%' . $filtroTexto . '%')
                ->orWhere('IPorigen', 'like', '%' . $filtroTexto . '%')
                ->orWhere('rol', 'like', '%' . $filtroTexto . '%')
                ->orWhere('accion_descripcion', 'like', '%' . $filtroTexto . '%')
                ->orWhere('responsable_nombre', 'like', '%' . $filtroTexto . '%');
        });
    }

    $logs = $query
        ->latest('id')
        ->paginate(10)
        ->withQueryString();

    $accionesDisponibles = LogSeguridad::query()
        ->whereNotNull('accion_descripcion')
        ->distinct()
        ->orderBy('accion_descripcion')
        ->pluck('accion_descripcion');

    return view('administrador.logs', [
        'logs' => $logs,
        'accionesDisponibles' => $accionesDisponibles,
    ]);
});

Route::get('/administrador/asistente-consultas', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $pendientes = AsistenteSinRespuesta::query()
        ->orderByDesc('created_at')
        ->paginate(15)
        ->withQueryString();

    $totalSinRevisar = AsistenteSinRespuesta::query()
        ->where('revisado', false)
        ->count();

    return view('administrador.asistente-consultas', [
        'pendientes' => $pendientes,
        'totalSinRevisar' => $totalSinRevisar,
    ]);
});

Route::post('/administrador/asistente-consultas/{id}/marcar-revisado', function (Request $request, int $id) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    AsistenteSinRespuesta::query()->where('id', $id)->update(['revisado' => true]);

    return back()->with('admin_action_success', 'Consulta marcada como revisada.');
});

Route::delete('/administrador/asistente-consultas/{id}', function (Request $request, int $id) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    AsistenteSinRespuesta::query()->where('id', $id)->delete();

    return back()->with('admin_action_success', 'Consulta eliminada.');
});

Route::get('/administrador/fallas', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $query = Error::query();

    $filtroCodigo = trim((string) $request->query('codigo', ''));
    $filtroDesde = trim((string) $request->query('desde', ''));
    $filtroHasta = trim((string) $request->query('hasta', ''));
    $filtroTexto = trim((string) $request->query('q', ''));

    if ($filtroCodigo !== '') {
        $query->where('codigo', $filtroCodigo);
    }

    if ($filtroDesde !== '') {
        $query->whereDate('fecha_hora', '>=', $filtroDesde);
    }

    if ($filtroHasta !== '') {
        $query->whereDate('fecha_hora', '<=', $filtroHasta);
    }

    if ($filtroTexto !== '') {
        $query->where(function ($q) use ($filtroTexto): void {
            $q->where('mensaje', 'like', '%' . $filtroTexto . '%')
                ->orWhere('url', 'like', '%' . $filtroTexto . '%')
                ->orWhere('track_trace', 'like', '%' . $filtroTexto . '%');
        });
    }

    $fallas = $query
        ->latest('id')
        ->paginate(10)
        ->withQueryString();

    $codigosDisponibles = Error::query()
        ->whereNotNull('codigo')
        ->where('codigo', '<>', '')
        ->distinct()
        ->orderBy('codigo')
        ->pluck('codigo');

    return view('administrador.fallas', [
        'fallas' => $fallas,
        'codigosDisponibles' => $codigosDisponibles,
    ]);
});

Route::get('/administrador/logs/exportar-json', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $query = LogSeguridad::query();

    $filtroAccion = trim((string) $request->query('accion', ''));
    $filtroDesde = trim((string) $request->query('desde', ''));
    $filtroHasta = trim((string) $request->query('hasta', ''));
    $filtroTexto = trim((string) $request->query('q', ''));

    if ($filtroAccion !== '') {
        $query->where('accion_descripcion', $filtroAccion);
    }

    if ($filtroDesde !== '') {
        $query->whereDate('fechahora', '>=', $filtroDesde);
    }

    if ($filtroHasta !== '') {
        $query->whereDate('fechahora', '<=', $filtroHasta);
    }

    if ($filtroTexto !== '') {
        $query->where(function ($q) use ($filtroTexto): void {
            $q->where('descripcion', 'like', '%' . $filtroTexto . '%')
                ->orWhere('IPorigen', 'like', '%' . $filtroTexto . '%')
                ->orWhere('rol', 'like', '%' . $filtroTexto . '%')
                ->orWhere('accion_descripcion', 'like', '%' . $filtroTexto . '%')
                ->orWhere('responsable_nombre', 'like', '%' . $filtroTexto . '%');
        });
    }

    $logs = $query
        ->latest('id')
        ->get()
        ->map(function ($log) {
            return [
                'id' => $log->id,
                'fecha_hora' => $log->fechahora ? \Illuminate\Support\Carbon::parse($log->fechahora)->format('Y-m-d H:i:s') : null,
                'accion_id' => $log->accion_id,
                'accion' => $log->accion_descripcion,
                'rol' => $log->rol,
                'ip_origen' => $log->IPorigen,
                'responsable' => $log->responsable_nombre,
                'descripcion' => $log->descripcion,
            ];
        })
        ->values();

    $nombreArchivo = 'logs-seguridad-' . now()->format('Ymd-His') . '.json';

    return response()->json([
        'generado_en' => now()->format('Y-m-d H:i:s'),
        'filtros_aplicados' => [
            'accion' => $filtroAccion !== '' ? $filtroAccion : null,
            'desde' => $filtroDesde !== '' ? $filtroDesde : null,
            'hasta' => $filtroHasta !== '' ? $filtroHasta : null,
            'texto' => $filtroTexto !== '' ? $filtroTexto : null,
        ],
        'total_registros' => $logs->count(),
        'logs' => $logs,
    ], 200, [
        'Content-Disposition' => 'attachment; filename="' . $nombreArchivo . '"',
    ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
});

Route::get('/administrador/contenido-web', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    return redirect('/administrador/contenido/quienes-somos');
});

Route::get('/administrador/contenido/quienes-somos', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $contenidoWeb = ContenidoWeb::latest('id')->first();

    return view('administrador.contenido-quienes-somos', [
        'contenidoWeb' => $contenidoWeb,
    ]);
});

Route::get('/administrador/contenido/profesiones', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesiones = Profesion::query()
        ->orderBy('titulo')
        ->get();

    return view('administrador.contenido-profesiones', [
        'profesiones' => $profesiones,
    ]);
});

Route::get('/administrador/contenido/servicios', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $servicios = Servicio::with('profesion')
        ->orderBy('titulo')
        ->paginate(15)
        ->withQueryString();

    return view('administrador.contenido-servicios', [
        'servicios' => $servicios,
    ]);
});

Route::get('/administrador/contenido/asistente-virtual', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    return redirect('/administrador/contenido/asistente-virtual/ver-faqs');
});

Route::get('/administrador/contenido/asistente-virtual/agregar-faq', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_BURBUJA],
        [
            'palabras_clave' => [],
            'respuesta' => '¡Hola!, ¿te puedo ayudar en algo?',
            'orden' => 1,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_PANEL_INICIO],
        [
            'palabras_clave' => [],
            'respuesta' => 'Hola, soy el asistente virtual. Puedo ayudarte con servicios, profesionales, turnos, ubicación y contacto.',
            'orden' => 2,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_ERROR],
        [
            'palabras_clave' => [],
            'respuesta' => 'Lo siento, no tengo una respuesta exacta para eso.',
            'orden' => 3,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_NOMBRE],
        [
            'palabras_clave' => [],
            'respuesta' => 'Clara',
            'orden' => 4,
            'activo' => true,
        ]
    );

    return view('administrador.contenido-asistente-agregar-faq');
});

Route::get('/administrador/contenido/asistente-virtual/agregar-chips', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $chipsAsistenteAdmin = FaqAsistente::query()
        ->where('intencion', FAQ_INTENCION_CHIPS)
        ->orderBy('orden')
        ->orderBy('id')
        ->get();

    return view('administrador.contenido-asistente-agregar-chips', [
        'chipsAsistenteAdmin' => $chipsAsistenteAdmin,
    ]);
});

Route::get('/administrador/contenido/asistente-virtual/ver-faqs', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $filtroIntencion = mb_strtolower(trim((string) $request->query('intencion', '')));
    $perPage = (int) $request->query('per_page', 10);
    if (!in_array($perPage, [10, 20, 50], true)) {
        $perPage = 10;
    }

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_BURBUJA],
        [
            'palabras_clave' => [],
            'respuesta' => '¡Hola!, ¿te puedo ayudar en algo?',
            'orden' => 1,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_PANEL_INICIO],
        [
            'palabras_clave' => [],
            'respuesta' => 'Hola, soy el asistente virtual. Puedo ayudarte con servicios, profesionales, turnos, ubicación y contacto.',
            'orden' => 2,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_ERROR],
        [
            'palabras_clave' => [],
            'respuesta' => 'Lo siento, no tengo una respuesta exacta para eso.',
            'orden' => 3,
            'activo' => true,
        ]
    );

    FaqAsistente::query()->firstOrCreate(
        ['intencion' => FAQ_INTENCION_NOMBRE],
        [
            'palabras_clave' => [],
            'respuesta' => 'Clara',
            'orden' => 4,
            'activo' => true,
        ]
    );

    $faqsAsistenteQuery = FaqAsistente::query()
        ->where(function ($query): void {
            $query->whereNull('intencion')
                ->orWhere('intencion', '!=', FAQ_INTENCION_CHIPS);
        });

    if ($filtroIntencion !== '') {
        $faqsAsistenteQuery->whereRaw('LOWER(COALESCE(intencion, "")) like ?', ['%' . $filtroIntencion . '%']);
    }

    $faqsAsistente = $faqsAsistenteQuery
        ->orderBy('orden')
        ->orderBy('id')
        ->paginate($perPage)
        ->withQueryString();

    return view('administrador.contenido-asistente-ver-faqs', [
        'faqsAsistente' => $faqsAsistente,
        'filtroIntencion' => $filtroIntencion,
        'perPage' => $perPage,
    ]);
});

Route::get('/administrador/emails', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $tipos = [
        'turno_otorgado' => [
            'titulo' => 'Turno otorgado',
            'mensaje_inicio' => 'Se asigno tu turno con los siguientes datos:',
            'mensaje_final' => 'Si necesitas reprogramar o cancelar, por favor comunicate por WhatsApp al 343-4632444',
        ],
        'turno_cancelado' => [
            'titulo' => 'Turno cancelado',
            'mensaje_inicio' => 'Te informamos que tu turno fue cancelado con los siguientes datos:',
            'mensaje_final' => 'Si necesitas coordinar un nuevo turno, por favor comunicate por WhatsApp al 343-4632444',
        ],
        'turno_reprogramado' => [
            'titulo' => 'Turno reprogramado',
            'mensaje_inicio' => 'Te informamos que tu turno fue reprogramado con los siguientes datos:',
            'mensaje_final' => 'Si tenes dudas sobre este cambio, por favor comunicate por WhatsApp al 343-4632444',
        ],
    ];

    foreach ($tipos as $tipo => $defecto) {
        Notificacion::query()->firstOrCreate(
            ['tipo' => $tipo],
            [
                'mensaje_inicio' => $defecto['mensaje_inicio'],
                'mensaje_final' => $defecto['mensaje_final'],
            ]
        );
    }

    $notificaciones = Notificacion::query()
        ->whereIn('tipo', array_keys($tipos))
        ->get()
        ->keyBy('tipo');

    return view('administrador.emails', [
        'tipos' => $tipos,
        'notificaciones' => $notificaciones,
    ]);
});

Route::post('/administrador/emails', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validado = $request->validate([
        'turno_otorgado_mensaje_inicio' => ['required', 'string', 'max:5000'],
        'turno_otorgado_mensaje_final' => ['required', 'string', 'max:5000'],
        'turno_cancelado_mensaje_inicio' => ['required', 'string', 'max:5000'],
        'turno_cancelado_mensaje_final' => ['required', 'string', 'max:5000'],
        'turno_reprogramado_mensaje_inicio' => ['required', 'string', 'max:5000'],
        'turno_reprogramado_mensaje_final' => ['required', 'string', 'max:5000'],
    ]);

    $tipos = ['turno_otorgado', 'turno_cancelado', 'turno_reprogramado'];

    foreach ($tipos as $tipo) {
        Notificacion::query()->updateOrCreate(
            ['tipo' => $tipo],
            [
                'mensaje_inicio' => $validado[$tipo . '_mensaje_inicio'],
                'mensaje_final' => $validado[$tipo . '_mensaje_final'],
            ]
        );
    }

    return redirect('/administrador/emails')
        ->with('admin_action_success', 'Los textos de emails se actualizaron correctamente.');
});

Route::get('/administrador/bugs', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $bugs = Bug::query()
        ->with('fotoBug')
        ->orderByDesc('id')
        ->get();

    return view('administrador.reportes-bugs', [
        'bugs' => $bugs,
    ]);
});

Route::post('/administrador/bugs/{bug}/marcar-visto', function (Request $request, Bug $bug) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $estadoActual = mb_strtolower(trim((string) $bug->estado));

    if ($estadoActual === 'pendiente') {
        $bug->update([
            'estado' => 'Visto',
        ]);

        return redirect('/administrador/bugs')
            ->with('admin_action_success', 'El reporte se marcó como visto.');
    }

    return redirect('/administrador/bugs')
        ->with('admin_action_info', 'El reporte ya estaba en estado visto.');
});

Route::post('/administrador/contenido-web', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'quienessomos' => ['required', 'string', 'max:5000'],
    ]);

    $contenidoWeb = ContenidoWeb::latest('id')->first();

    if ($contenidoWeb) {
        $contenidoWeb->update([
            'quienessomos' => $validated['quienessomos'],
        ]);
    } else {
        ContenidoWeb::create([
            'quienessomos' => $validated['quienessomos'],
        ]);
    }

    return redirect('/administrador/contenido/quienes-somos')
        ->with('admin_action_success', 'Se actualizó la sección Quienes Somos correctamente.');
});

Route::post('/administrador/contenido-web/faqs', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'intencion' => ['nullable', 'string', 'max:100'],
        'palabras_clave' => ['nullable', 'string', 'max:1000'],
        'respuesta' => ['required', 'string', 'max:5000'],
        'orden' => ['required', 'integer', 'min:0', 'max:65535'],
        'activo' => ['required', 'in:0,1'],
    ]);

    $palabrasClave = collect(explode(',', (string) ($validated['palabras_clave'] ?? '')))
        ->map(fn ($palabra) => trim($palabra))
        ->filter(fn ($palabra) => $palabra !== '')
        ->unique()
        ->values()
        ->all();

    $intencionNormalizada = mb_strtolower(trim((string) ($validated['intencion'] ?? '')));
    $esMensajeSistema = in_array($intencionNormalizada, [
        FAQ_INTENCION_BURBUJA,
        FAQ_INTENCION_PANEL_INICIO,
        FAQ_INTENCION_ERROR,
        FAQ_INTENCION_NOMBRE,
        FAQ_INTENCION_CHIPS,
    ], true);

    if (!$esMensajeSistema && empty($palabrasClave)) {
        return back()
            ->withErrors(['palabras_clave' => 'Ingresá al menos una palabra clave válida para respuestas del chat.'])
            ->withInput();
    }

    FaqAsistente::create([
        'intencion' => $intencionNormalizada !== '' ? $intencionNormalizada : null,
        'palabras_clave' => $palabrasClave,
        'respuesta' => $validated['respuesta'],
        'orden' => (int) $validated['orden'],
        'activo' => (bool) $validated['activo'],
    ]);

    if ($intencionNormalizada === FAQ_INTENCION_CHIPS) {
        return redirect('/administrador/contenido/asistente-virtual/agregar-chips')
            ->with('admin_action_success', 'ui_chip creado correctamente.');
    }

    return redirect('/administrador/contenido/asistente-virtual/agregar-faq')
        ->with('admin_action_success', 'FAQ del asistente creada correctamente.');
});

Route::put('/administrador/contenido-web/faqs/{faqAsistente}', function (Request $request, FaqAsistente $faqAsistente) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'intencion' => ['nullable', 'string', 'max:100'],
        'palabras_clave' => ['nullable', 'string', 'max:1000'],
        'respuesta' => ['required', 'string', 'max:5000'],
        'orden' => ['required', 'integer', 'min:0', 'max:65535'],
        'activo' => ['required', 'in:0,1'],
    ]);

    $palabrasClave = collect(explode(',', (string) ($validated['palabras_clave'] ?? '')))
        ->map(fn ($palabra) => trim($palabra))
        ->filter(fn ($palabra) => $palabra !== '')
        ->unique()
        ->values()
        ->all();

    $intencionNormalizada = mb_strtolower(trim((string) ($validated['intencion'] ?? '')));
    $esMensajeSistema = in_array($intencionNormalizada, [
        FAQ_INTENCION_BURBUJA,
        FAQ_INTENCION_PANEL_INICIO,
        FAQ_INTENCION_ERROR,
        FAQ_INTENCION_NOMBRE,
        FAQ_INTENCION_CHIPS,
    ], true);

    if (!$esMensajeSistema && empty($palabrasClave)) {
        return back()
            ->withErrors(['palabras_clave' => 'Ingresá al menos una palabra clave válida para respuestas del chat.'])
            ->withInput();
    }

    $faqAsistente->update([
        'intencion' => $intencionNormalizada !== '' ? $intencionNormalizada : null,
        'palabras_clave' => $palabrasClave,
        'respuesta' => $validated['respuesta'],
        'orden' => (int) $validated['orden'],
        'activo' => (bool) $validated['activo'],
    ]);

    if ($intencionNormalizada === FAQ_INTENCION_CHIPS) {
        return redirect('/administrador/contenido/asistente-virtual/agregar-chips')
            ->with('admin_action_success', 'ui_chip actualizado correctamente.');
    }

    return redirect('/administrador/contenido/asistente-virtual/ver-faqs')
        ->with('admin_action_success', 'FAQ del asistente actualizada correctamente.');
});

Route::delete('/administrador/contenido-web/faqs/{faqAsistente}', function (Request $request, FaqAsistente $faqAsistente) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $esChip = mb_strtolower(trim((string) ($faqAsistente->intencion ?? ''))) === FAQ_INTENCION_CHIPS;

    $faqAsistente->delete();

    if ($esChip) {
        return redirect('/administrador/contenido/asistente-virtual/agregar-chips')
            ->with('admin_action_success', 'ui_chip borrado correctamente.');
    }

    return redirect('/administrador/contenido/asistente-virtual/ver-faqs')
        ->with('admin_action_success', 'FAQ del asistente borrada correctamente.');
});

Route::get('/administrador/profesiones/crear', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    return view('administrador.crear-profesion');
});

Route::post('/administrador/profesiones', function (Request $request) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'titulo'                => ['required', 'string', 'max:255', 'unique:profesiones,titulo'],
        'visible_en_formulario' => ['required', 'in:0,1'],
    ]);

    $profesionCreada = Profesion::create([
        'titulo'                => $validated['titulo'],
        'visible_en_formulario' => (bool) $validated['visible_en_formulario'],
    ]);

    $registrarLogSeguridad(
        $request,
        'Creación nueva profesion',
        'Se creó la profesión ID ' . $profesionCreada->id . ' (' . $profesionCreada->titulo . ').'
    );

    return redirect('/administrador/contenido/profesiones')
        ->with('admin_action_success', 'Profesión creada correctamente.');
});

Route::get('/administrador/profesiones/{profesion}/editar', function (Request $request, Profesion $profesion) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    return view('administrador.editar-profesion', [
        'profesion' => $profesion,
    ]);
});

Route::put('/administrador/profesiones/{profesion}', function (Request $request, Profesion $profesion) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'titulo'                => ['required', 'string', 'max:255', 'unique:profesiones,titulo,' . $profesion->id],
        'visible_en_formulario' => ['required', 'in:0,1'],
    ]);

    $tituloAnterior = (string) $profesion->titulo;
    $visibleAnterior = (bool) $profesion->visible_en_formulario;

    $profesion->update([
        'titulo'                => $validated['titulo'],
        'visible_en_formulario' => (bool) $validated['visible_en_formulario'],
    ]);

    $visibleNuevo = (bool) $profesion->visible_en_formulario;
    if ($visibleAnterior && !$visibleNuevo) {
        $registrarLogSeguridad(
            $request,
            'Baja profesion',
            'Se dio de baja la profesión ID ' . $profesion->id . ' (' . $profesion->titulo . ').'
        );
    } elseif (!$visibleAnterior && $visibleNuevo) {
        $registrarLogSeguridad(
            $request,
            'Alta profesion',
            'Se dio de alta la profesión ID ' . $profesion->id . ' (' . $profesion->titulo . ').'
        );
    } else {
        $registrarLogSeguridad(
            $request,
            'Edición datos profesion',
            'Se editó la profesión ID ' . $profesion->id . '. Título: "' . $tituloAnterior . '" -> "' . $profesion->titulo . '".'
        );
    }

    return redirect('/administrador/contenido/profesiones')
        ->with('admin_action_success', 'Profesión actualizada correctamente.');
});

Route::get('/administrador/servicios/crear', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesiones = Profesion::query()
        ->orderBy('titulo')
        ->get();

    return view('administrador.crear-servicio', [
        'profesiones' => $profesiones,
    ]);
});

Route::post('/administrador/servicios', function (Request $request) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $contenidoWeb = ContenidoWeb::latest('id')->first();

    if (!$contenidoWeb) {
        $contenidoWeb = ContenidoWeb::create([
            'quienessomos' => '',
        ]);
    }

    $validated = $request->validate([
        'titulo'       => ['required', 'string', 'max:255'],
        'descripcion'  => ['required', 'string', 'max:3000'],
        'estado'       => ['required', 'in:activo,inactivo'],
        'visibleenweb' => ['required', 'in:si,no'],
        'profesion_id' => ['required', 'exists:profesiones,id'],
        'foto'         => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
    ]);

    $fotoDefaultId = Foto::query()
        ->where('ruta', 'storage/fotos/Servicio.jpg')
        ->value('id');

    if (!$fotoDefaultId) {
        $fotoDefaultId = Foto::query()->create([
            'extension' => 'jpg',
            'tamanio_bytes' => 0,
            'nombre' => 'Servicio',
            'mime_type' => 'image/jpeg',
            'ruta' => 'storage/fotos/Servicio.jpg',
        ])->id;
    }

    $datos = [
        'titulo'       => $validated['titulo'],
        'descripcion'  => $validated['descripcion'],
        'estado'       => $validated['estado'],
        'visibleenweb' => $validated['visibleenweb'],
        'contenidoweb_id' => (int) $contenidoWeb->id,
        'profesion_id' => (int) $validated['profesion_id'],
        'foto_id'      => (int) $fotoDefaultId,
    ];

    $fotoFile = $request->file('foto');
    if ($fotoFile) {
        $directorio = public_path('images/servicios');
        File::ensureDirectoryExists($directorio);

        $extension     = $fotoFile->getClientOriginalExtension();
        $mimeType      = $fotoFile->getClientMimeType();
        $tamanioBytes  = $fotoFile->getSize();
        $nombreArchivo = uniqid('serv_', true) . '.' . $extension;

        $fotoFile->move($directorio, $nombreArchivo);

        $foto = Foto::create([
            'extension'     => $extension,
            'nombre'        => pathinfo($nombreArchivo, PATHINFO_FILENAME),
            'mime_type'     => $mimeType,
            'tamanio_bytes' => $tamanioBytes,
            'ruta'          => 'images/servicios/' . $nombreArchivo,
        ]);

        $datos['foto_id'] = $foto->id;
    }

    $servicioCreado = Servicio::create($datos);

    $registrarLogSeguridad(
        $request,
        'Creación nuevo servicio',
        'Se creó el servicio ID ' . $servicioCreado->id . ' (' . $servicioCreado->titulo . ') para la profesión ID ' . (int) $servicioCreado->profesion_id . '.'
    );

    return redirect('/administrador/contenido/servicios')
        ->with('admin_action_success', 'Servicio creado correctamente.');
});

Route::get('/administrador/servicios/{servicio}/editar', function (Request $request, Servicio $servicio) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesiones = Profesion::query()
        ->orderBy('titulo')
        ->get();

    return view('administrador.editar-servicio', [
        'servicio' => $servicio,
        'profesiones' => $profesiones,
    ]);
});

Route::put('/administrador/servicios/{servicio}', function (Request $request, Servicio $servicio) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'titulo'       => ['required', 'string', 'max:255'],
        'descripcion'  => ['required', 'string', 'max:3000'],
        'estado'       => ['required', 'in:activo,inactivo'],
        'visibleenweb' => ['required', 'in:si,no'],
        'profesion_id' => ['required', 'exists:profesiones,id'],
        'foto'         => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
    ]);

    $tituloAnterior = (string) $servicio->titulo;
    $estadoAnterior = mb_strtolower(trim((string) $servicio->estado));

    $datos = [
        'titulo'       => $validated['titulo'],
        'descripcion'  => $validated['descripcion'],
        'estado'       => $validated['estado'],
        'visibleenweb' => $validated['visibleenweb'],
        'profesion_id' => (int) $validated['profesion_id'],
    ];

    $fotoFile = $request->file('foto');
    if ($fotoFile) {
        $directorio = public_path('images/servicios');
        File::ensureDirectoryExists($directorio);

        $extension     = $fotoFile->getClientOriginalExtension();
        $mimeType      = $fotoFile->getClientMimeType();
        $tamanioBytes  = $fotoFile->getSize();
        $nombreArchivo = uniqid('serv_', true) . '.' . $extension;

        $fotoFile->move($directorio, $nombreArchivo);

        $foto = Foto::create([
            'extension'     => $extension,
            'nombre'        => pathinfo($nombreArchivo, PATHINFO_FILENAME),
            'mime_type'     => $mimeType,
            'tamanio_bytes' => $tamanioBytes,
            'ruta'          => 'images/servicios/' . $nombreArchivo,
        ]);

        $datos['foto_id'] = $foto->id;
    }

    $servicio->update($datos);

    $estadoNuevo = mb_strtolower(trim((string) $servicio->estado));
    if ($estadoAnterior === 'activo' && $estadoNuevo !== 'activo') {
        $registrarLogSeguridad(
            $request,
            'Baja servicio',
            'Se dio de baja el servicio ID ' . $servicio->id . ' (' . $servicio->titulo . ').'
        );
    } elseif ($estadoAnterior !== 'activo' && $estadoNuevo === 'activo') {
        $registrarLogSeguridad(
            $request,
            'Alta servicio',
            'Se dio de alta el servicio ID ' . $servicio->id . ' (' . $servicio->titulo . ').'
        );
    } else {
        $registrarLogSeguridad(
            $request,
            'Edición datos servicio',
            'Se editó el servicio ID ' . $servicio->id . '. Título: "' . $tituloAnterior . '" -> "' . $servicio->titulo . '".'
        );
    }

    return redirect('/administrador/contenido/servicios')
        ->with('admin_action_success', 'Servicio actualizado correctamente.');
});

Route::get('/administrador/profesionales', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $filtroTexto = trim((string) $request->query('q', ''));
    $filtroEstado = trim((string) $request->query('estado', ''));

    $profesionales = Profesional::with([
            'persona',
            'profesion',
            'profesiones',
            'credencialProfesional',
        ])
        ->when($filtroTexto !== '', function ($query) use ($filtroTexto) {
            $query->where(function ($subQuery) use ($filtroTexto): void {
                $subQuery->where('dni', 'like', '%' . $filtroTexto . '%')
                    ->orWhere('correo', 'like', '%' . $filtroTexto . '%')
                    ->orWhere('matricula', 'like', '%' . $filtroTexto . '%')
                    ->orWhereHas('persona', function ($qPersona) use ($filtroTexto): void {
                        $qPersona->where('nombre', 'like', '%' . $filtroTexto . '%')
                            ->orWhere('apellido', 'like', '%' . $filtroTexto . '%')
                            ->orWhere('dni', 'like', '%' . $filtroTexto . '%');
                    })
                    ->orWhereHas('profesion', function ($qProfesion) use ($filtroTexto): void {
                        $qProfesion->where('titulo', 'like', '%' . $filtroTexto . '%');
                    });
            });
        })
        ->when($filtroEstado === 'activos', fn ($query) => $query->where('baja_id', 1))
        ->when($filtroEstado === 'inactivos', fn ($query) => $query->where('baja_id', '!=', 1))
        ->orderBy('id')
        ->paginate(10)
        ->withQueryString();

    return view('administrador.profesionales', [
        'profesionales' => $profesionales,
    ]);
});

Route::post('/administrador/profesionales/{profesional}/baja', function (Request $request, Profesional $profesional) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $estabaDeBaja = (int) $profesional->baja_id !== 1;
    $nombreCompleto = trim(($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? ''));

    DB::transaction(function () use ($profesional): void {
        if ((int) $profesional->baja_id !== 1) {
            $profesional->baja_id = 1;
            $profesional->save();
            return;
        }

        $baja = Baja::create([
            'descripcion' => 'Baja realizada por administrador',
        ]);

        $profesional->baja_id = $baja->id;
        $profesional->save();
    });

    if ($estabaDeBaja) {
        $registrarLogSeguridad(
            $request,
            'Alta profesional',
            'Se dio de alta al profesional ID ' . $profesional->id . ' (' . $nombreCompleto . ').'  
        );

        return redirect('/administrador/profesionales')
            ->with('admin_action_success', 'Profesional reactivado correctamente.');
    }

    $registrarLogSeguridad(
        $request,
        'Baja profesional',
        'Se dio de baja al profesional ID ' . $profesional->id . ' (' . $nombreCompleto . ').'  
    );

    return redirect('/administrador/profesionales')
        ->with('admin_action_success', 'Profesional dado de baja correctamente.');
});

Route::get('/administrador/profesionales/nuevo', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesiones = Profesion::query()
        ->orderBy('titulo')
        ->get();

    $servicios = Servicio::query()
        ->where('estado', 'activo')
        ->orderBy('titulo')
        ->get();

    return view('administrador.nuevo-profesional', [
        'profesiones' => $profesiones,
        'servicios' => $servicios,
    ]);
});

Route::get('/administrador/profesionales/buscar-persona', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $dni = trim((string) $request->query('dni', ''));

    if (strlen($dni) < 3) {
        return response()->json(['encontrada' => false, 'persona' => null]);
    }

    $persona = Persona::where('dni', $dni)->first();

    if (!$persona) {
        return response()->json(['encontrada' => false, 'persona' => null]);
    }

    $celular = trim((string) ($persona->telefonos()->value('telefono') ?? ''));

    return response()->json([
        'encontrada' => true,
        'persona' => [
            'nombre'   => $persona->nombre,
            'apellido' => $persona->apellido,
            'cuil'     => $persona->cuil,
            'celular'  => $celular,
            'fechanac' => $persona->fechanac,
        ],
    ]);
});

Route::get('/administrador/profesionales/{profesional}/editar', function (Request $request, Profesional $profesional) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesiones = Profesion::query()
        ->orderBy('titulo')
        ->get();

    $servicios = Servicio::query()
        ->where('estado', 'activo')
        ->orderBy('titulo')
        ->get();

    $serviciosSeleccionados = $profesional->servicios()
        ->pluck('servicios.id')
        ->all();

    return view('administrador.editar-profesional', [
        'profesional' => $profesional,
        'profesiones' => $profesiones,
        'servicios' => $servicios,
        'serviciosSeleccionados' => $serviciosSeleccionados,
    ]);
});

Route::get('/administrador/profesionales/{profesional}/asignaciones', function (Request $request, Profesional $profesional) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesional->load(['persona', 'profesion', 'profesiones', 'servicios']);

    $servicios = Servicio::query()
        ->where('estado', 'activo')
        ->orderBy('titulo')
        ->get();

    $serviciosSeleccionados = $profesional->servicios()
        ->pluck('servicios.id')
        ->all();

    return view('administrador.asignaciones-profesional', [
        'profesional' => $profesional,
        'servicios' => $servicios,
        'serviciosSeleccionados' => $serviciosSeleccionados,
    ]);
});

Route::put('/administrador/profesionales/{profesional}/asignaciones', function (Request $request, Profesional $profesional) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $validated = $request->validate([
        'servicio_ids' => ['required', 'array', 'min:1'],
        'servicio_ids.*' => [
            'required',
            Rule::exists('servicios', 'id')->where(fn ($q) => $q
                ->where('profesion_id', (int) $profesional->profesion_id)
                ->where('estado', 'activo')),
        ],
    ]);

    $profesional->profesiones()->sync([(int) $profesional->profesion_id]);

    $profesional->servicios()->sync($validated['servicio_ids']);

    $profesionActual = Profesion::query()->find((int) $profesional->profesion_id)?->titulo ?? 'Sin profesión';
    $nombreCompleto = trim(($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? ''));

    $registrarLogSeguridad(
        $request,
        'Edición datos profesional',
        'Se actualizaron servicios del profesional ID ' . $profesional->id . ' (' . $nombreCompleto . ') en la profesión "' . $profesionActual . '". Servicios asignados: ' . implode(', ', $validated['servicio_ids']) . '.'
    );

    return redirect('/administrador/profesionales')
        ->with('admin_action_success', 'Se actualizaron los servicios del profesional.');
});

Route::put('/administrador/profesionales/{profesional}', function (Request $request, Profesional $profesional) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $profesionBloqueadaId = (int) $profesional->profesion_id;

    $validated = $request->validate([
        'dni' => ['required', 'string', 'max:20', 'regex:/^[0-9A-Za-z]{7,20}$/', Rule::unique('personas', 'dni')->ignore((int) ($profesional->persona_id ?? 0))],
        'nombre' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'apellido' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
        'cuil' => ['required', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'fechanac' => ['required', 'date'],
        'correo' => ['required', 'email', 'max:255'],
        'matricula' => [
            'required',
            'string',
            'max:100',
            Rule::unique('profesionales', 'matricula')
                ->where(fn ($q) => $q->where('profesion_id', $profesionBloqueadaId))
                ->ignore((int) $profesional->id),
        ],
        'profesion_id' => ['required', Rule::in([$profesionBloqueadaId])],
        'foto' => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
        'quitar_foto' => ['nullable', 'boolean'],
        'servicio_ids' => ['required', 'array', 'min:1'],
        'servicio_ids.*' => [
            'required',
            Rule::exists('servicios', 'id')->where(fn ($q) => $q
                ->where('profesion_id', $profesionBloqueadaId)
                ->where('estado', 'activo')),
        ],
    ]);

    $dniAnterior = (string) $profesional->dni;
    $cuilAnterior = (string) ($profesional->persona?->cuil ?? '');
    $fechanacAnterior = (string) ($profesional->persona?->fechanac ?? '');
    $nombreAnterior = (string) ($profesional->persona?->nombre ?? '');
    $apellidoAnterior = (string) ($profesional->persona?->apellido ?? '');
    $correoAnterior = $profesional->correo;
    $matriculaAnterior = (string) $profesional->matricula;
    $profesionAnteriorId = (int) $profesional->profesion_id;
    $usuarioCredencialAnterior = (string) ($profesional->credencialProfesional?->usuario ?? '');

    $nuevoUsuarioCredencial = $validated['dni'] . '-' . $profesionBloqueadaId;
    if (
        $nuevoUsuarioCredencial !== ''
        && $nuevoUsuarioCredencial !== $usuarioCredencialAnterior
        && CredencialProfesional::where('usuario', $nuevoUsuarioCredencial)
            ->where('id', '!=', (int) ($profesional->credencialprofesional_id ?? 0))
            ->exists()
    ) {
        return back()
            ->withErrors(['profesion_id' => 'El usuario generado para esa combinación de DNI y profesión ya existe.'])
            ->withInput();
    }

    if ($profesional->persona) {
        $profesional->persona->update([
            'dni' => $validated['dni'],
            'nombre' => $validated['nombre'],
            'apellido' => $validated['apellido'],
            'cuil' => $validated['cuil'],
            'fechanac' => $validated['fechanac'],
        ]);

        Profesional::query()
            ->where('persona_id', (int) $profesional->persona->id)
            ->update([
                'dni' => $validated['dni'],
                'updated_at' => now(),
            ]);

        DB::table('personas_telefonos')
            ->where('persona_id', (int) $profesional->persona->id)
            ->update([
                'dni' => $validated['dni'],
                'updated_at' => now(),
            ]);

        $fotoFile = $request->file('foto');
        if ($fotoFile) {
            $directorio = public_path('images/profesionales');
            File::ensureDirectoryExists($directorio);

            $extension     = $fotoFile->getClientOriginalExtension();
            $mimeType      = $fotoFile->getClientMimeType();
            $tamanioBytes  = $fotoFile->getSize();
            $nombreArchivo = uniqid('prof_', true) . '.' . $extension;

            $fotoFile->move($directorio, $nombreArchivo);

            $foto = Foto::create([
                'extension'     => $extension,
                'nombre'        => pathinfo($nombreArchivo, PATHINFO_FILENAME),
                'mime_type'     => $mimeType,
                'tamanio_bytes' => $tamanioBytes,
                'ruta'          => 'images/profesionales/' . $nombreArchivo,
            ]);

            $profesional->persona->update([
                'foto_id' => $foto->id,
            ]);
        } elseif ((bool) ($validated['quitar_foto'] ?? false)) {
            $fotoDefault = Foto::firstOrCreate(
                ['ruta' => 'images/avatar_default.png'],
                [
                    'extension'     => 'png',
                    'nombre'        => 'avatar_default',
                    'mime_type'     => 'image/png',
                    'tamanio_bytes' => 136788,
                ]
            );

            $profesional->persona->update([
                'foto_id' => $fotoDefault->id,
            ]);
        }
    }

    $profesional->update([
        'dni' => $validated['dni'],
        'matricula' => $validated['matricula'],
        'correo' => $validated['correo'],
    ]);

    $profesional->profesiones()->sync([$profesionBloqueadaId]);

    $profesional->servicios()->sync($validated['servicio_ids']);

    if ($profesional->credencialProfesional && $nuevoUsuarioCredencial !== '') {
        $profesional->credencialProfesional->update([
            'usuario' => $nuevoUsuarioCredencial,
        ]);
    }

    $nombreCompleto = trim(($profesional->persona?->nombre ?? '') . ' ' . ($profesional->persona?->apellido ?? ''));

    $registrarLogSeguridad(
        $request,
        'Edición datos profesional',
        'Se editó el profesional ID ' . $profesional->id . ' (' . $nombreCompleto . '). Nombre: "' . $nombreAnterior . '" -> "' . $validated['nombre'] . '", Apellido: "' . $apellidoAnterior . '" -> "' . $validated['apellido'] . '", CUIL: "' . $cuilAnterior . '" -> "' . $validated['cuil'] . '", Fecha Nac.: "' . $fechanacAnterior . '" -> "' . $validated['fechanac'] . '", Correo: "' . $correoAnterior . '" -> "' . $validated['correo'] . '", Matrícula: "' . $matriculaAnterior . '" -> "' . $validated['matricula'] . '", Profesión ID: "' . $profesionAnteriorId . '", Usuario credencial: "' . $usuarioCredencialAnterior . '" -> "' . $nuevoUsuarioCredencial . '".'
    );

    if ($dniAnterior !== (string) $validated['dni']) {
        $personaResponsableId = Administrador::query()
            ->where('credencialprofesional_id', (int) $request->session()->get('personal_credencial_id', 0))
            ->value('persona_id');

        if ($personaResponsableId) {
            LogSeguridad::create([
                'descripcion' => 'Cambio de DNI del profesional ID ' . $profesional->id . ': "' . $dniAnterior . '" -> "' . $validated['dni'] . '".',
                'fechahora' => now(),
                'IPorigen' => (string) $request->ip(),
                'rol' => 'ADMIN',
                'persona_id' => (int) $personaResponsableId,
                'accion_id' => 33,
            ]);
        }
    }

    return redirect('/administrador/profesionales')
        ->with('admin_action_success', 'Profesional actualizado correctamente.');
});

Route::post('/administrador/profesionales', function (Request $request) use ($registrarLogSeguridad, $validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $dni = trim((string) $request->input('dni', ''));
    $dniVerificado = trim((string) $request->input('dni_verificado', ''));

    // Verificar en DB si la persona ya existe (autoritativo, no se confía en el campo oculto)
    $personaExistente = Persona::where('dni', $dni)->first();

    $reglas = [
        'dni'          => ['required', 'string', 'max:20', 'regex:/^[0-9A-Za-z]{7,20}$/'],
        'dni_verificado' => ['required', 'string', 'max:20'],
        'correo'       => ['required', 'email', 'max:255'],
        'foto'         => ['nullable', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
        'matricula'    => ['required', 'string', 'max:100'],
        'celular'      => ['required', 'string', 'max:30', 'regex:/^[0-9]+$/'],
        'profesion_id' => ['required', 'exists:profesiones,id'],
        'servicio_ids' => ['required', 'array', 'min:1'],
        'servicio_ids.*' => [
            'required',
            Rule::exists('servicios', 'id')->where(fn ($q) => $q
                ->where('profesion_id', $request->input('profesion_id'))
                ->where('estado', 'activo')),
        ],
        'contra' => ['required', 'string', 'min:6'],
    ];

    if (!$personaExistente) {
        $reglas = array_merge($reglas, [
            'nombre'   => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
            'apellido' => ['required', 'string', 'max:100', 'regex:/^[\pL\s]+$/u'],
            'cuil'     => ['required', 'string', 'max:30', 'regex:/^[0-9]+$/'],
            'fechanac' => ['required', 'date'],
        ]);
    }

    $validated = $request->validate($reglas);

    if ($dniVerificado === '' || $dniVerificado !== $dni) {
        return back()
            ->withErrors(['dni' => 'Debes verificar el DNI antes de guardar el profesional.'])
            ->withInput();
    }

    // El usuario se auto-genera como DNI-CODIGO_PROFESION (id de profesion)
    $usuario = $validated['dni'] . '-' . $validated['profesion_id'];

    // Verificar que no exista ya un profesional con ese DNI y esa profesión
    if (Profesional::where('dni', $validated['dni'])->where('profesion_id', $validated['profesion_id'])->exists()) {
        return back()
            ->withErrors(['dni' => "Este profesional ya está registrado en esa profesión."])
            ->withInput();
    }

    // Verificar que el usuario auto-generado no esté en uso
    if (CredencialProfesional::where('usuario', $usuario)->exists()) {
        return back()
            ->withErrors(['dni' => "El usuario auto-generado «{$usuario}» ya existe. Verifique los datos."])
            ->withInput();
    }

    $fotoFile = $request->file('foto');

    $profesionalCreado = null;

    DB::transaction(function () use ($validated, $fotoFile, $personaExistente, $usuario, &$profesionalCreado): void {
        if ($personaExistente) {
            $persona = $personaExistente;

            if ($fotoFile) {
                $directorio = public_path('images/profesionales');
                File::ensureDirectoryExists($directorio);

                $extension     = $fotoFile->getClientOriginalExtension();
                $mimeType      = $fotoFile->getClientMimeType();
                $tamanioBytes  = $fotoFile->getSize();
                $nombreArchivo = uniqid('prof_', true) . '.' . $extension;

                $fotoFile->move($directorio, $nombreArchivo);

                $foto = Foto::create([
                    'extension'     => $extension,
                    'nombre'        => pathinfo($nombreArchivo, PATHINFO_FILENAME),
                    'mime_type'     => $mimeType,
                    'tamanio_bytes' => $tamanioBytes,
                    'ruta'          => 'images/profesionales/' . $nombreArchivo,
                ]);

                $persona->update([
                    'foto_id' => $foto->id,
                ]);
            }
        } else {
            if ($fotoFile) {
                $directorio = public_path('images/profesionales');
                File::ensureDirectoryExists($directorio);

                // Capturar metadatos ANTES de mover el archivo (move() invalida el objeto)
                $extension     = $fotoFile->getClientOriginalExtension();
                $mimeType      = $fotoFile->getClientMimeType();
                $tamanioBytes  = $fotoFile->getSize();
                $nombreArchivo = uniqid('prof_', true) . '.' . $extension;

                $fotoFile->move($directorio, $nombreArchivo);

                $foto = Foto::create([
                    'extension'     => $extension,
                    'nombre'        => pathinfo($nombreArchivo, PATHINFO_FILENAME),
                    'mime_type'     => $mimeType,
                    'tamanio_bytes' => $tamanioBytes,
                    'ruta'          => 'images/profesionales/' . $nombreArchivo,
                ]);
            } else {
                $foto = Foto::firstOrCreate(
                    ['ruta' => 'images/avatar_default.png'],
                    [
                        'extension'     => 'png',
                        'nombre'        => 'avatar_default',
                        'mime_type'     => 'image/png',
                        'tamanio_bytes' => 136788,
                    ]
                );
            }

            $persona = Persona::create([
                'dni'      => $validated['dni'],
                'nombre'   => $validated['nombre'],
                'apellido' => $validated['apellido'],
                'cuil'     => $validated['cuil'],
                'fechanac' => $validated['fechanac'],
                'foto_id'  => $foto->id,
            ]);
        }

        $telefonoExistente = $persona->telefonos()->first();
        if ($telefonoExistente) {
            $telefonoExistente->update([
                'telefono' => $validated['celular'],
            ]);
        } else {
            $telefonoNuevo = Telefono::create([
                'telefono' => $validated['celular'],
            ]);

            DB::table('personas_telefonos')->insertOrIgnore([
                'dni' => (string) $persona->dni,
                'persona_id' => (int) $persona->id,
                'telefono_id' => (int) $telefonoNuevo->id,
                'created_at' => now(),
                'updated_at' => now(),
            ]);
        }

        $credencial = CredencialProfesional::create([
            'usuario' => $usuario,
            'contra'  => Hash::make($validated['contra']),
            'rol'     => 'PROFESIONAL',
        ]);

        $profesional = Profesional::create([
            'profesion_id'             => (int) $validated['profesion_id'],
            'matricula'                => $validated['matricula'],
            'correo'                   => $validated['correo'],
            'dni'                      => $validated['dni'],
            'persona_id'               => $persona->id,
            'credencialprofesional_id' => $credencial->id,
            'baja_id'                  => 1,
        ]);

        Agenda::create([
            'profesional_id' => $profesional->id,
            'estado'         => 'activo',
            'duracionturno'  => 30,
        ]);

        $profesional->profesiones()->sync([(int) $validated['profesion_id']]);
        $profesional->servicios()->sync($validated['servicio_ids']);
        $profesionalCreado = $profesional;
    });

    if ($profesionalCreado) {
        $nombreCompleto = trim(($profesionalCreado->persona?->nombre ?? '') . ' ' . ($profesionalCreado->persona?->apellido ?? ''));

        $registrarLogSeguridad(
            $request,
            'Creación nuevo profesional',
            'Se creó el profesional ID ' . $profesionalCreado->id . ' (' . $nombreCompleto . ').'
        );
    }

    return redirect('/administrador/profesionales')->with('admin_action_success', 'Profesional creado correctamente.');
});

Route::get('/cliente/dashboard', function (Request $request) {
    if (!$request->session()->get('cliente_auth')) {
        return redirect('/login/cliente')->with('login_error', 'Debes iniciar sesión como cliente.');
    }

    $clienteCorreoSesion = trim((string) $request->session()->get('cliente_correo', ''));

    $cliente = Cliente::query()
        ->with(['persona.telefonos', 'credencialCliente'])
        ->whereHas('credencialCliente', fn ($query) => $query->where('correo', $clienteCorreoSesion))
        ->first();

    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $quienesSomos = $contenidoWeb?->quienessomos;
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));

    $profesionales = Profesional::with(['persona.Foto', 'profesion', 'profesiones'])
        ->where('baja_id', 1)
        ->get()
        ->groupBy('persona_id')
        ->map(function ($profesionalesPorPersona) {
            $profesionalBase = $profesionalesPorPersona->first();

            $profesionesCombinadas = $profesionalesPorPersona
                ->flatMap(function ($profesional) {
                    return $profesional->profesiones
                        ->push($profesional->profesion)
                        ->filter();
                })
                ->unique('id')
                ->values();

            $profesionalBase->setRelation('profesiones', $profesionesCombinadas);

            return $profesionalBase;
        })
        ->values();

    $servicios = Servicio::with('foto')
        ->where('estado', 'activo')
        ->where('visibleenweb', 'si')
        ->get();

    $profesiones = Profesion::query()
        ->where('visible_en_formulario', true)
        ->orderBy('titulo')
        ->get();

    $modalidades = Modalidad::query()
        ->orderBy('descripcion')
        ->get();

    $mensajeBurbujaAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_BURBUJA)
        ->value('respuesta') ?? '¡Hola, soy Clara!, ¿te puedo ayudar en algo?';

    $mensajeInicioPanelAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_PANEL_INICIO)
        ->value('respuesta') ?? 'Hola, soy Clara, la asistente virtual de Abogadas del Litoral. Escribí una palabra clave con el tema con el que necesites ayuda.';

    $nombreAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_NOMBRE)
        ->value('respuesta') ?? 'Clara';

    $chipsAsistente = FaqAsistente::query()
        ->where('activo', true)
        ->where('intencion', FAQ_INTENCION_CHIPS)
        ->orderBy('orden')
        ->orderBy('id')
        ->pluck('respuesta')
        ->flatMap(fn ($respuesta) => explode("\n", (string) $respuesta))
        ->map(fn ($c) => trim($c))
        ->filter(fn ($c) => $c !== '')
        ->unique()
        ->values()
        ->all();

    $nombreClienteForm = trim((string) ($cliente?->persona?->nombre ?? $request->session()->get('cliente_nombre', '')));
    $apellidoClienteForm = trim((string) ($cliente?->persona?->apellido ?? $request->session()->get('cliente_apellido', '')));
    $correoClienteForm = trim((string) ($cliente?->correo ?? $cliente?->credencialCliente?->correo ?? $clienteCorreoSesion));
    $celularClienteForm = trim((string) ($cliente?->persona?->telefonos?->first()?->telefono ?? $request->session()->get('cliente_celular', '')));

    return view('cliente.dashboard', [
        'quienesSomos'  => $quienesSomos,
        'versionSitio' => $versionSitio,
        'profesionales' => $profesionales,
        'servicios'     => $servicios,
        'profesiones'   => $profesiones,
        'modalidades'   => $modalidades,
        'mensajeBurbujaAsistente' => $mensajeBurbujaAsistente,
        'mensajeInicioPanelAsistente' => $mensajeInicioPanelAsistente,
        'nombreAsistente' => $nombreAsistente,
        'chipsAsistente' => $chipsAsistente,
        'nombreClienteForm' => $nombreClienteForm,
        'apellidoClienteForm' => $apellidoClienteForm,
        'correoClienteForm' => $correoClienteForm,
        'celularClienteForm' => $celularClienteForm,
    ]);
});

Route::get('/cliente/mis-turnos', function (Request $request) {
    if (!$request->session()->get('cliente_auth')) {
        return redirect('/login/cliente')->with('login_error', 'Debes iniciar sesión como cliente.');
    }

    $clienteIdSesion = (int) $request->session()->get('cliente_id', 0);
    $clienteCorreoSesion = trim((string) $request->session()->get('cliente_correo', ''));

    $periodo = trim((string) $request->query('periodo', 'todos'));
    $estadoId = (int) $request->query('estado_id', 0);

    $turnosQuery = Turno::query()
        ->with([
            'profesional.persona',
            'profesional.profesion',
            'servicio',
            'modalidad',
            'estadoTurno',
        ])
        ->where(function ($query) use ($clienteIdSesion, $clienteCorreoSesion): void {
            if ($clienteIdSesion > 0) {
                $query->where('cliente_id', $clienteIdSesion);
            }

            if ($clienteCorreoSesion !== '') {
                if ($clienteIdSesion > 0) {
                    $query->orWhere('correo', $clienteCorreoSesion);
                } else {
                    $query->where('correo', $clienteCorreoSesion);
                }
            }
        });

    if ($periodo === 'proximos') {
        $turnosQuery->where('inicio', '>=', now());
    } elseif ($periodo === 'pasados') {
        $turnosQuery->where('inicio', '<', now());
    }

    if ($estadoId > 0) {
        $turnosQuery->where('estadoturno_id', $estadoId);
    }

    $turnos = $turnosQuery
        ->orderByDesc('inicio')
        ->paginate(5)
        ->withQueryString();

    $estadosTurno = EstadoTurno::query()
        ->whereNotIn(DB::raw('LOWER(TRIM(descripcion))'), ['cliente ausente', 'cliente presente'])
        ->orderBy('descripcion')
        ->get(['id', 'descripcion']);

    $contenidoWeb = ContenidoWeb::latest('id')->first();
    $versionSitio = trim((string) ($contenidoWeb?->version ?? '1.0.0'));
    
    return view('cliente.mis-turnos', [
        'turnos' => $turnos,
        'estadosTurno' => $estadosTurno,
        'filtroPeriodo' => $periodo,
        'filtroEstadoId' => $estadoId,
        'versionSitio' => $versionSitio,
    ]);
});

Route::get('/cliente/ayuda', function (Request $request) {
    if (!$request->session()->get('cliente_auth')) {
        return redirect('/login/cliente')->with('login_error', 'Debes iniciar sesión como cliente.');
    }

    return view('cliente.ayuda');
});

Route::post('/cliente/turnos/{turno}/cancelar', function (Request $request, Turno $turno) {
    if (!$request->session()->get('cliente_auth')) {
        return redirect('/login/cliente')->with('login_error', 'Debes iniciar sesión como cliente.');
    }

    $clienteIdSesion = (int) $request->session()->get('cliente_id', 0);
    $clienteCorreoSesion = trim((string) $request->session()->get('cliente_correo', ''));

    $esTurnoDelCliente = ($clienteIdSesion > 0 && (int) $turno->cliente_id === $clienteIdSesion)
        || ($clienteCorreoSesion !== '' && strcasecmp((string) ($turno->correo ?? ''), $clienteCorreoSesion) === 0);

    if (!$esTurnoDelCliente) {
        return back()->with('turno_error', 'No podés cancelar un turno que no te pertenece.');
    }

    $estadoCanceladoId = (int) (EstadoTurno::query()
        ->whereRaw('LOWER(TRIM(descripcion)) = ?', ['cancelado'])
        ->value('id') ?? 0);

    if ($estadoCanceladoId <= 0) {
        return back()->with('turno_error', 'No se encontró el estado Cancelado.');
    }

    if ((int) $turno->estadoturno_id === $estadoCanceladoId) {
        return back()->with('turno_error', 'El turno seleccionado ya está cancelado.');
    }

    $turno->update([
        'estadoturno_id' => $estadoCanceladoId,
    ]);

    $turno->loadMissing(['profesional.persona', 'servicio', 'modalidad']);

    $correoProfesional = trim((string) ($turno->profesional?->correo ?? ''));
    if ($correoProfesional !== '') {
        $nombreProfesional = trim((string) (($turno->profesional?->persona?->nombre ?? '') . ' ' . ($turno->profesional?->persona?->apellido ?? '')));
        $nombreCliente = trim((string) ($turno->nombrecompleto ?? 'Cliente'));
        $fechaTurno = $turno->inicio ? $turno->inicio->format('d/m/Y') : '-';
        $horaTurno = $turno->inicio ? $turno->inicio->format('H:i') : '-';
        $servicioTurno = trim((string) ($turno->servicio?->titulo ?? 'Servicio'));
        $modalidadTurno = trim((string) ($turno->modalidad?->descripcion ?? 'Modalidad'));

        $asunto = 'Cancelación de turno por cliente';
        $cuerpo = "Hola " . ($nombreProfesional !== '' ? $nombreProfesional : 'profesional') . ",\n\n"
            . "El cliente {$nombreCliente} canceló su turno.\n"
            . "- Fecha: {$fechaTurno}\n"
            . "- Hora: {$horaTurno}\n"
            . "- Servicio: {$servicioTurno}\n"
            . "- Modalidad: {$modalidadTurno}\n";

        try {
            Mail::raw($cuerpo, function ($message) use ($correoProfesional, $asunto): void {
                $message->to($correoProfesional)->subject($asunto);
            });
        } catch (\Throwable $e) {
            logger()->warning('No se pudo enviar correo al profesional por cancelación de cliente.', [
                'turno_id' => (int) $turno->id,
                'correo_profesional' => $correoProfesional,
                'error' => $e->getMessage(),
            ]);
        }
    }

    return back()->with('turno_success', 'El turno se canceló correctamente y se notificó al profesional.');
});

Route::get('/logout', function (Request $request) use ($registrarLogSeguridadDirecto) {
    $sesionPersonalActiva = (bool) $request->session()->get('personal_auth', false);
    $eraCliente = (bool) $request->session()->get('cliente_auth', false) && !$sesionPersonalActiva;

    if ($eraCliente) {
        $clienteId = (int) $request->session()->get('cliente_id', 0);
        $personaId = Cliente::query()->where('id', $clienteId)->value('persona_id');

        $registrarLogSeguridadDirecto(
            $personaId ? (int) $personaId : null,
            'CLIENTE',
            (string) $request->ip(),
            'Cerró sesión',
            'El cliente ID ' . $clienteId . ' cerró sesión.'
        );
    } else {
        $credencialId = (int) $request->session()->get('personal_credencial_id', 0);
        $rolSesion = strtoupper((string) $request->session()->get('personal_rol', 'PROFESIONAL'));

        if ($rolSesion === 'ADMIN') {
            $admin = Administrador::query()->where('credencialprofesional_id', $credencialId)->first();

            $registrarLogSeguridadDirecto(
                $admin?->persona_id ? (int) $admin->persona_id : null,
                'ADMIN',
                (string) $request->ip(),
                'Cerró sesión',
                'La administradora ID ' . (int) ($admin?->id ?? 0) . ' cerró sesión.'
            );
        } else {
            $profesional = Profesional::query()->where('credencialprofesional_id', $credencialId)->first();

            $registrarLogSeguridadDirecto(
                $profesional?->persona_id ? (int) $profesional->persona_id : null,
                'PROFESIONAL',
                (string) $request->ip(),
                'Cerró sesión',
                'El profesional ID ' . (int) ($profesional?->id ?? 0) . ' cerró sesión.'
            );
        }
    }

    $request->session()->invalidate();
    $request->session()->regenerateToken();

    $redirectPath = $eraCliente ? '/login/cliente' : '/login/personal';

    return redirect($redirectPath)->with('logout_success', 'Sesión cerrada correctamente.');
});

Route::get('/administrador/ayuda', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    return view('administrador.ayuda');
});

Route::get('/administrador/backups', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $disk = \Illuminate\Support\Facades\Storage::disk('local');
    $backupFolder = trim((string) config('backup.backup.name', 'Laravel'), '/');

    $archivos = collect($disk->files($backupFolder))
        ->filter(fn (string $file): bool => str_ends_with(strtolower($file), '.zip'))
        ->sortByDesc(fn (string $file): int => $disk->lastModified($file))
        ->map(fn (string $file): array => [
            'nombre'  => basename($file),
            'ruta'    => $file,
            'tamanio' => round($disk->size($file) / 1024, 2),
            'fecha'   => \Illuminate\Support\Carbon::createFromTimestampUTC($disk->lastModified($file))
                ->setTimezone((string) config('app.timezone', 'America/Argentina/Buenos_Aires'))
                ->format('d/m/Y H:i:s'),
        ])
        ->values();

    return view('administrador.backups', compact('archivos'));
});

Route::get('/administrador/backups/descargar', function (Request $request) use ($validarSesionAdmin) {
    if ($respuesta = $validarSesionAdmin($request)) {
        return $respuesta;
    }

    $archivo = (string) $request->query('archivo', '');

    if ($archivo === '') {
        abort(400, 'Archivo no especificado.');
    }

    $disk = \Illuminate\Support\Facades\Storage::disk('local');

    // Validar que el archivo está dentro de la carpeta de backups (evitar path traversal)
    $backupFolder = trim((string) config('backup.backup.name', 'Laravel'), '/');
    $archivoNormalizado = ltrim(str_replace('\\', '/', $archivo), '/');

    if (!str_starts_with($archivoNormalizado, $backupFolder . '/') || !str_ends_with(strtolower($archivoNormalizado), '.zip')) {
        abort(403, 'Acceso no permitido.');
    }

    if (!$disk->exists($archivoNormalizado)) {
        abort(404, 'Archivo no encontrado.');
    }

    return response()->streamDownload(function () use ($disk, $archivoNormalizado): void {
        echo $disk->get($archivoNormalizado);
    }, basename($archivoNormalizado), [
        'Content-Type' => 'application/zip',
    ]);
});
