Resto de las vistas y plantillas del sistema

This commit is contained in:
Lucho
2026-06-24 16:21:44 -03:00
parent 7a622f469d
commit ff2fa9b70f
39 changed files with 6142 additions and 103 deletions
@@ -0,0 +1,186 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asignar Servicios - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-8">
<h1 class="h4 mb-2">Asignar servicios</h1>
<p class="text-muted mb-3">
Profesional: <strong>{{ $profesional->persona?->nombre }} {{ $profesional->persona?->apellido }}</strong>
(DNI: {{ $profesional->dni }})
</p>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/profesionales/{{ $profesional->id }}/asignaciones" class="row g-3">
@csrf
@method('PUT')
<div class="col-12">
<label class="form-label">Profesión actual</label>
<input type="text" class="form-control bg-light" value="{{ $profesional->profesion?->titulo ?? '-' }}" readonly>
</div>
<div class="col-12">
<label class="form-label d-block mb-2">Servicios</label>
<div id="servicios-container" class="d-flex flex-column gap-2">
@foreach($servicios as $servicio)
<div class="form-check" data-profesion-id="{{ $servicio->profesion_id }}">
<input
class="form-check-input"
type="checkbox"
id="servicio_{{ $servicio->id }}"
name="servicio_ids[]"
value="{{ $servicio->id }}"
@checked(in_array((string) $servicio->id, array_map('strval', old('servicio_ids', $serviciosSeleccionados ?? [])), true))
>
<label class="form-check-label" for="servicio_{{ $servicio->id }}">
{{ $servicio->titulo }}
</label>
</div>
@endforeach
</div>
<div class="form-text">Solo se muestran servicios activos de la profesión actual.</div>
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/profesionales" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar asignaciones</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
<script>
(function () {
const serviciosContainer = document.getElementById('servicios-container');
const profesionId = '{{ (string) $profesional->profesion_id }}';
if (!serviciosContainer) {
return;
}
const items = Array.from(serviciosContainer.querySelectorAll('[data-profesion-id]'));
const filtrarServicios = () => {
items.forEach((item) => {
const input = item.querySelector('input[type="checkbox"]');
const visible = item.dataset.profesionId === profesionId;
item.classList.toggle('d-none', !visible);
if (!visible && input) {
input.checked = false;
}
});
};
filtrarServicios();
})();
</script>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,172 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Consultas sin respuesta - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/bugs">Bugs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="d-flex justify-content-between align-items-center mb-3">
<div>
<h1 class="h4 mb-0">Consultas sin respuesta del asistente</h1>
@if($totalSinRevisar > 0)
<span class="badge bg-warning text-dark mt-1">{{ $totalSinRevisar }} sin revisar</span>
@endif
</div>
<a href="/administrador/contenido/asistente-virtual" class="btn btn-outline-secondary btn-sm">Ir al asistente</a>
</div>
@if(session('admin_action_success'))
<div class="alert alert-success alert-dismissible fade show" role="alert">
{{ session('admin_action_success') }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Cerrar"></button>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th style="width: 80px;">ID</th>
<th>Consulta del usuario</th>
<th style="width: 180px;">Recibida</th>
<th style="width: 110px;">Estado</th>
<th style="width: 180px;">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($pendientes as $item)
<tr @class(['table-warning' => !$item->revisado])>
<td>{{ $item->id }}</td>
<td>{{ $item->consulta }}</td>
<td>
@if($item->created_at)
{{ \Illuminate\Support\Carbon::parse($item->created_at)->format('d/m/Y H:i') }}
@else
-
@endif
</td>
<td>
@if($item->revisado)
<span class="badge bg-success">Revisado</span>
@else
<span class="badge bg-warning text-dark">Pendiente</span>
@endif
</td>
<td class="d-flex gap-2 flex-wrap">
@if(!$item->revisado)
<form method="POST" action="/administrador/asistente-consultas/{{ $item->id }}/marcar-revisado">
@csrf
<button type="submit" class="btn btn-outline-success btn-sm">Marcar revisado</button>
</form>
@endif
<form method="POST" action="/administrador/asistente-consultas/{{ $item->id }}">
@csrf
@method('DELETE')
<button
type="submit"
class="btn btn-outline-danger btn-sm"
onclick="return confirm('¿Eliminar esta consulta?')"
>Eliminar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted py-4">No hay consultas sin respuesta registradas.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($pendientes->hasPages())
<div class="px-3 py-2">
{{ $pendientes->links() }}
</div>
@endif
</div>
</div>
</main>
<footer class="text-center py-3 text-muted small">
&copy; {{ date('Y') }} Abogadas Litoral. Panel de administración.
</footer>
</body>
</html>
@@ -0,0 +1,191 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Ayuda - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/bugs">Bugs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-xl-10">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Ayuda para usar el sistema (Administrador)</h1>
<a href="/administrador/dashboard" class="btn btn-outline-secondary btn-sm">Volver</a>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Inicio de sesión</h2>
<p class="mb-2">Para iniciar sesión primero debe poseer las credenciales de acceso.</p>
<p class="mb-0">En la vista de inicio de sesión de profesionales, el administrador depositará sus credenciales de acceso, y si son correctas podrá acceder al sistema.</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Recuperación de credenciales</h2>
<p class="mb-0">En caso de olvidar su usuario o contraseña, deberá presionar el botón debajo de la vista de inicio de sesión de profesionales "Recuperación para administrador" y luego de llenar un pequeño formulario, tendrá la posibilidad de cambiar su usuario y contraseña mediante un enlace que se le enviará a su correo electrónico (debe ser el mismo correo con el que está registrado en el sistema).</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Profesionales</h2>
<p class="mb-2">En este apartado se podrá visualizar todos los profesionales cargados en el sistema. Se podrá buscar a un profesional particular mediante el filtro en la parte superior.</p>
<p class="mb-2">Dentro de cada profesional habrá tres acciones posibles: Dar de baja/alta, editar datos y asignar nuevos servicios.</p>
<ul class="mb-0">
<li><strong>Dar de baja:</strong> Al dar de baja un profesional, este no podrá ingresar al sistema ni tampoco aparecerá en los formularios de turnos.</li>
<li><strong>Editar datos:</strong> Se podrá editar los datos personales de cada profesional.</li>
<li><strong>Asignar nuevos servicios:</strong> A medida que se agreguen nuevos servicios, se podrá asignar estos servicios a los profesionales que lo presten.</li>
</ul>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Mis Datos</h2>
<p class="mb-0">Aquí se podrá editar los datos del administrador. También se podrá configurar la pregunta y respuesta secreta del administrador (la cual se le pedirá en caso de querer recuperar sus credenciales).</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Contenido</h2>
<p class="mb-2">Este apartado contiene todo lo concerniente a la información que se mostrará en la página y otras configuraciones del sistema.</p>
<ul class="mb-0">
<li><strong>Quienes somos:</strong> Aquí se podrá editar el contenido del apartado informativo "Quienes somos".</li>
<li><strong>Profesiones:</strong> Aquí se podrá agregar, editar y dar de baja/alta las profesiones existentes y las nuevas.</li>
<li><strong>Servicios:</strong> Aquí se podrá agregar, editar y dar de baja/alta los servicios existentes y los nuevos.</li>
<li class="mt-2"><strong>Asistente virtual:</strong> Esta sección tiene la función de editar las características y respuestas del asistente virtual.
<ul class="mt-1">
<li><strong>Agregar nueva FAQ (preguntas frecuentes):</strong> Se podrá agregar una nueva respuesta del asistente virtual. Se le pedirá al usuario que ingrese un título, las palabras claves que puede llegar a utilizar el usuario, la prioridad (debe ser distinta a las prioridades existentes) y la respuesta que debe brindar el asistente.</li>
<li><strong>Editar FAQ existentes:</strong> Aquí se podrá editar cada una de las FAQ existentes. El título, palabras clave, respuestas, prioridad y dar de baja.</li>
<li><strong>Consultas sin respuestas:</strong> En este apartado se podrán visualizar las distintas preguntas de los usuarios para los que el asistente virtual no tiene una respuesta. De esta manera, se puede ir puliendo las respuestas y palabras claves del asistente virtual.</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Emails</h2>
<p class="mb-0">Acá se puede editar los mensajes que va a recibir el usuario al momento de que se le asigne un turno, se le reprograme un turno o se le cancele un turno.</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Logs</h2>
<p class="mb-0">En esta sección se podrá visualizar todos los logs del sistema (inicios de sesión, creación de profesionales, creación de clientes, pedidos de recuperación de clave, etc). También existe la posibilidad de descargar los logs en formato JSON para auditorías.</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Fallas</h2>
<p class="mb-0">En este apartado se podrán ver los distintos errores que puedan llegar desde el servidor.</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Bugs</h2>
<p class="mb-0">Aquí se podrá ver los distintos bugs que envíen los usuarios a través del botón de fallas 🐞. También se podrá marcar como "visto" cada uno de los bugs para llevar un checklist.</p>
</div>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<h2 class="h6">Backups</h2>
<p class="mb-0">Aquí se podrá visualizar los últimos 4 backups correspondientes a los últimos 4 domingos. Se podrá descargar un zip con todo el backup generado.</p>
</div>
</div>
<div class="card border shadow-sm">
<div class="card-body">
<h2 class="h6">¿Qué hacer en caso de detectar un error en la página?</h2>
<p class="mb-0">Si se encuentra con un error en el sitio, agradeceríamos que lo reporte con el botón que aparece en la parte superior de la página con el símbolo 🐞. Al apretarlo se sacará una foto a su pantalla automáticamente y se le pedirá que complete un pequeño formulario explicando que fue lo que ocurrió.</p>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,142 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Backups - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link active" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Backups de base de datos</h1>
<a href="/administrador/dashboard" class="btn btn-outline-secondary btn-sm">Volver</a>
</div>
<div class="alert alert-info" role="alert">
Los backups se generan automáticamente todos los días a las 16:00 hs y se conservan los últimos 4 archivos.
</div>
<div class="card border shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>Nombre del archivo</th>
<th style="width: 130px;">Tamaño</th>
<th style="width: 180px;">Fecha de creación</th>
<th style="width: 120px;" class="text-end">Acción</th>
</tr>
</thead>
<tbody>
@forelse($archivos as $archivo)
<tr>
<td>{{ $archivo['nombre'] }}</td>
<td>{{ $archivo['tamanio'] }} KB</td>
<td>{{ $archivo['fecha'] }}</td>
<td class="text-end">
<a
href="/administrador/backups/descargar?archivo={{ urlencode($archivo['ruta']) }}"
class="btn btn-sm btn-outline-primary"
download
>
Descargar
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-muted py-4">No hay backups disponibles.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,215 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asistente Virtual - Agregar Chips</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-3">Asistente Virtual</h1>
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-faq" class="nav-link">Agregar FAQ</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-chips" class="nav-link active" aria-current="page">Agregar chips</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/ver-faqs" class="nav-link">Ver FAQs</a>
</li>
</ul>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Agregar y administrar ui_chips</h2>
</div>
<div class="card-body border-bottom">
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3">
@csrf
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-7">
<label for="chip_respuesta" class="form-label">Texto del ui_chip</label>
<input type="text" id="chip_respuesta" name="respuesta" class="form-control" value="{{ old('respuesta') }}" maxlength="255" required placeholder="Ej: Quiero sacar un turno">
</div>
<div class="col-md-2">
<label for="chip_orden" class="form-label">Orden</label>
<input type="number" id="chip_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<label for="chip_activo" class="form-label">Estado</label>
<select id="chip_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-outline-primary w-100"></button>
</div>
</form>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>ui_chip</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($chipsAsistenteAdmin as $chip)
<tr>
<td>{{ $chip->id }}</td>
<td colspan="3">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-8">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $chip->respuesta }}" maxlength="255" required>
</div>
<div class="col-md-2">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $chip->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $chip->activo ? 'selected' : '' }}>Activo</option>
<option value="0" {{ !$chip->activo ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar este ui_chip?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted py-4">No hay ui_chips cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,166 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asistente Virtual - Agregar FAQ</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-3">Asistente Virtual</h1>
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-faq" class="nav-link active" aria-current="page">Agregar FAQ</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-chips" class="nav-link">Agregar chips</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/ver-faqs" class="nav-link">Ver FAQs</a>
</li>
</ul>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Agregar nueva FAQ</h2>
</div>
<div class="card-body">
<div class="alert alert-info py-2" role="alert">
Para configurar textos fijos del asistente, usá estas intenciones: <strong>ui_nombre</strong>, <strong>ui_burbuja</strong>, <strong>ui_panel_inicio</strong> y <strong>ui_error</strong>. En esas intenciones, las palabras clave son opcionales.
</div>
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3">
@csrf
<div class="col-md-4">
<label for="faq_intencion" class="form-label">Intención (opcional)</label>
<input type="text" id="faq_intencion" name="intencion" class="form-control" value="{{ old('intencion') }}" maxlength="100" placeholder="Ej: turnos">
</div>
<div class="col-md-4">
<label for="faq_orden" class="form-label">Orden</label>
<input type="number" id="faq_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-4">
<label for="faq_activo" class="form-label">Estado</label>
<select id="faq_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12">
<label for="faq_palabras_clave" class="form-label">Palabras clave (separadas por coma)</label>
<input type="text" id="faq_palabras_clave" name="palabras_clave" class="form-control" value="{{ old('palabras_clave') }}" placeholder="Ej: turno, consulta, formulario">
</div>
<div class="col-12">
<label for="faq_respuesta" class="form-label">Respuesta</label>
<textarea id="faq_respuesta" name="respuesta" class="form-control" rows="3" maxlength="5000" required>{{ old('respuesta') }}</textarea>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Agregar FAQ</button>
</div>
</form>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,238 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asistente Virtual - Ver FAQs</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-3">Asistente Virtual</h1>
<ul class="nav nav-tabs mb-4">
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-faq" class="nav-link">Agregar FAQ</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/agregar-chips" class="nav-link">Agregar chips</a>
</li>
<li class="nav-item">
<a href="/administrador/contenido/asistente-virtual/ver-faqs" class="nav-link active" aria-current="page">Ver FAQs</a>
</li>
</ul>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">FAQs del asistente</h2>
<a href="/administrador/asistente-consultas" class="btn btn-outline-secondary btn-sm">Consultas sin respuesta</a>
</div>
<div class="card-body border-bottom bg-white">
<form method="GET" action="/administrador/contenido/asistente-virtual/ver-faqs" class="row g-2 align-items-end">
<div class="col-md-6 col-lg-4">
<label for="filtro_intencion" class="form-label mb-1">Filtrar por intención</label>
<input
type="text"
id="filtro_intencion"
name="intencion"
class="form-control"
value="{{ $filtroIntencion ?? '' }}"
maxlength="100"
placeholder="Ej: turnos, ui_error"
>
</div>
<div class="col-md-3 col-lg-2">
<label for="per_page" class="form-label mb-1">Por página</label>
<select id="per_page" name="per_page" class="form-select">
<option value="10" {{ (int) ($perPage ?? 10) === 10 ? 'selected' : '' }}>10</option>
<option value="20" {{ (int) ($perPage ?? 10) === 20 ? 'selected' : '' }}>20</option>
<option value="50" {{ (int) ($perPage ?? 10) === 50 ? 'selected' : '' }}>50</option>
</select>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-outline-primary">Buscar</button>
</div>
@if(!empty($filtroIntencion))
<div class="col-auto">
<a href="/administrador/contenido/asistente-virtual/ver-faqs" class="btn btn-outline-secondary">Limpiar</a>
</div>
@endif
</form>
</div>
<div class="card-body p-0">
<div class="px-3 py-2 border-bottom bg-light small text-muted">
Mostrando {{ $faqsAsistente->firstItem() ?? 0 }} - {{ $faqsAsistente->lastItem() ?? 0 }} de {{ $faqsAsistente->total() }} FAQs
</div>
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Intención</th>
<th>Palabras clave</th>
<th>Respuesta</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($faqsAsistente as $faq)
<tr>
<td>{{ $faq->id }}</td>
<td colspan="5">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<div class="col-md-3">
<input type="text" name="intencion" class="form-control form-control-sm" value="{{ $faq->intencion }}" maxlength="100" placeholder="Intención">
</div>
<div class="col-md-3">
<input type="text" name="palabras_clave" class="form-control form-control-sm" value="{{ implode(', ', $faq->palabras_clave ?? []) }}" placeholder="Palabras clave">
</div>
<div class="col-md-4">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $faq->respuesta }}" maxlength="5000" required>
</div>
<div class="col-md-1">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $faq->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-1">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $faq->activo ? 'selected' : '' }}>Act.</option>
<option value="0" {{ !$faq->activo ? 'selected' : '' }}>Inact.</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar esta FAQ?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay FAQs cargadas para el asistente.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($faqsAsistente->hasPages())
<div class="px-3 py-2 border-top bg-white">
{{ $faqsAsistente->links() }}
</div>
@endif
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,333 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Asistente Virtual - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Asistente Virtual (FAQ)</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Asistente Virtual (FAQ)</h2>
</div>
<div class="card-body border-bottom">
<div class="alert alert-info py-2" role="alert">
Para configurar textos fijos del asistente, usé estas intenciones: <strong>ui_nombre</strong> (nombre del asistente), <strong>ui_burbuja</strong> (burbuja flotante), <strong>ui_panel_inicio</strong> (primer mensaje del panel) y <strong>ui_error</strong> (mensaje cuando no encuentra respuesta). En esas intenciones, las palabras clave son opcionales. Los <strong>ui_chips</strong> ahora se administran en la sección separada de abajo.
</div>
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3">
@csrf
<div class="col-md-4">
<label for="faq_intencion" class="form-label">Intención (opcional)</label>
<input type="text" id="faq_intencion" name="intencion" class="form-control" value="{{ old('intencion') }}" maxlength="100" placeholder="Ej: turnos">
</div>
<div class="col-md-4">
<label for="faq_orden" class="form-label">Orden</label>
<input type="number" id="faq_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-4">
<label for="faq_activo" class="form-label">Estado</label>
<select id="faq_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12">
<label for="faq_palabras_clave" class="form-label">Palabras clave (separadas por coma, opcional para intenciones ui_*)</label>
<input type="text" id="faq_palabras_clave" name="palabras_clave" class="form-control" value="{{ old('palabras_clave') }}" placeholder="Ej: turno, consulta, formulario">
</div>
<div class="col-12">
<label for="faq_respuesta" class="form-label">Respuesta</label>
<textarea id="faq_respuesta" name="respuesta" class="form-control" rows="3" maxlength="5000" required>{{ old('respuesta') }}</textarea>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Agregar FAQ</button>
</div>
</form>
</div>
<div class="card-body border-bottom">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="h6 mb-0">ui_chips (sugerencias rápidas)</h3>
</div>
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3 mb-3">
@csrf
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-7">
<label for="chip_respuesta" class="form-label">Texto del ui_chip</label>
<input type="text" id="chip_respuesta" name="respuesta" class="form-control" value="{{ old('respuesta') }}" maxlength="255" required placeholder="Ej: Quiero sacar un turno">
</div>
<div class="col-md-2">
<label for="chip_orden" class="form-label">Orden</label>
<input type="number" id="chip_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<label for="chip_activo" class="form-label">Estado</label>
<select id="chip_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-outline-primary w-100"></button>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>ui_chip</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($chipsAsistenteAdmin as $chip)
<tr>
<td>{{ $chip->id }}</td>
<td colspan="3">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-8">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $chip->respuesta }}" maxlength="255" required>
</div>
<div class="col-md-2">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $chip->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $chip->activo ? 'selected' : '' }}>Activo</option>
<option value="0" {{ !$chip->activo ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar este ui_chip?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted py-4">No hay ui_chips cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="card-body p-0">
<div class="p-3 border-bottom bg-white">
<form method="GET" action="/administrador/contenido/asistente-virtual" class="row g-2 align-items-end">
<div class="col-md-6 col-lg-4">
<label for="filtro_intencion" class="form-label mb-1">Filtrar FAQs por intención</label>
<input
type="text"
id="filtro_intencion"
name="intencion"
class="form-control"
value="{{ $filtroIntencion ?? '' }}"
maxlength="100"
placeholder="Ej: turnos, ui_error"
>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-outline-primary">Buscar</button>
</div>
@if(!empty($filtroIntencion))
<div class="col-auto">
<a href="/administrador/contenido/asistente-virtual" class="btn btn-outline-secondary">Limpiar</a>
</div>
@endif
</form>
</div>
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Intención</th>
<th>Palabras clave</th>
<th>Respuesta</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($faqsAsistente as $faq)
<tr>
<td>{{ $faq->id }}</td>
<td colspan="5">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<div class="col-md-3">
<input type="text" name="intencion" class="form-control form-control-sm" value="{{ $faq->intencion }}" maxlength="100" placeholder="Intención">
</div>
<div class="col-md-3">
<input type="text" name="palabras_clave" class="form-control form-control-sm" value="{{ implode(', ', $faq->palabras_clave ?? []) }}" placeholder="Palabras clave">
</div>
<div class="col-md-4">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $faq->respuesta }}" maxlength="5000" required>
</div>
<div class="col-md-1">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $faq->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-1">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $faq->activo ? 'selected' : '' }}>Act.</option>
<option value="0" {{ !$faq->activo ? 'selected' : '' }}>Inact.</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar esta FAQ?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay FAQs cargadas para el asistente.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($faqsAsistente->hasPages())
<div class="px-3 py-2 border-top bg-white">
{{ $faqsAsistente->links() }}
</div>
@endif
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,151 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Profesiones - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Profesiones</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Agregar, editar o dar de baja Profesión</h2>
<a href="/administrador/profesiones/crear" class="btn btn-sm btn-primary">Agregar profesión</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Título</th>
<th>Visible en formulario</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($profesiones as $profesion)
<tr>
<td>{{ $profesion->id }}</td>
<td>{{ $profesion->titulo }}</td>
<td>{{ $profesion->visible_en_formulario ? 'Sí' : 'No' }}</td>
<td class="text-end">
<div class="d-inline-flex gap-2">
<a href="/administrador/profesiones/{{ $profesion->id }}/editar" class="btn btn-sm btn-outline-primary">Editar</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-muted py-4">No hay profesiones cargadas.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,130 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Quienes Somos - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Quienes Somos</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Editar Quienes Somos</h2>
</div>
<div class="card-body">
<form action="/administrador/contenido-web" method="POST" class="row g-3">
@csrf
<div class="col-12">
<label for="quienessomos" class="form-label">Texto visible en la sección Quienes Somos</label>
<textarea id="quienessomos" name="quienessomos" class="form-control" rows="5">{{ old('quienessomos', $contenidoWeb?->quienessomos ?? '') }}</textarea>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,161 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Servicios - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Servicios</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Agregar, editar o dar de baja Servicio</h2>
<a href="/administrador/servicios/crear" class="btn btn-sm btn-primary">Agregar servicio</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Título</th>
<th>Profesión</th>
<th>Estado</th>
<th>Visible en web</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($servicios as $servicio)
<tr>
<td>{{ $servicio->id }}</td>
<td>{{ $servicio->titulo }}</td>
<td>{{ $servicio->profesion?->titulo ?? '-' }}</td>
<td>{{ $servicio->estado }}</td>
<td>{{ ($servicio->visibleenweb ?? 'si') === 'si' ? 'Si' : 'No' }}</td>
<td class="text-end">
<div class="d-inline-flex gap-2">
<a href="/administrador/servicios/{{ $servicio->id }}/editar" class="btn btn-sm btn-outline-primary">Editar</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-muted py-4">No hay servicios cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($servicios->hasPages())
<div class="px-3 py-2 border-top bg-white">
{{ $servicios->links() }}
</div>
@endif
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,415 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Contenido - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Gestión de Contenido</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="row g-4">
<div class="col-12" id="seccion-quienes-somos">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Editar Quienes Somos</h2>
</div>
<div class="card-body">
<form action="/administrador/contenido-web" method="POST" class="row g-3">
@csrf
<div class="col-12">
<label for="quienessomos" class="form-label">Texto visible en la sección Quienes Somos</label>
<textarea id="quienessomos" name="quienessomos" class="form-control" rows="5">{{ old('quienessomos', $contenidoWeb?->quienessomos ?? '') }}</textarea>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-12" id="seccion-profesiones">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Agregar, editar o dar de baja Profesión</h2>
<a href="/administrador/profesiones/crear" class="btn btn-sm btn-primary">Agregar profesión</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Título</th>
<th>Visible en formulario</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($profesiones as $profesion)
<tr>
<td>{{ $profesion->id }}</td>
<td>{{ $profesion->titulo }}</td>
<td>{{ $profesion->visible_en_formulario ? 'Sí' : 'No' }}</td>
<td class="text-end">
<div class="d-inline-flex gap-2">
<a href="/administrador/profesiones/{{ $profesion->id }}/editar" class="btn btn-sm btn-outline-primary">Editar</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="text-center text-muted py-4">No hay profesiones cargadas.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12" id="seccion-servicios">
<div class="card border shadow-sm">
<div class="card-header bg-white d-flex justify-content-between align-items-center">
<h2 class="h5 mb-0">Agregar, editar o dar de baja Servicio</h2>
<a href="/administrador/servicios/crear" class="btn btn-sm btn-primary">Agregar servicio</a>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Título</th>
<th>Profesión</th>
<th>Estado</th>
<th>Visible en web</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($servicios as $servicio)
<tr>
<td>{{ $servicio->id }}</td>
<td>{{ $servicio->titulo }}</td>
<td>{{ $servicio->profesion?->titulo ?? '-' }}</td>
<td>{{ $servicio->estado }}</td>
<td>{{ ($servicio->visibleenweb ?? 'si') === 'si' ? 'Si' : 'No' }}</td>
<td class="text-end">
<div class="d-inline-flex gap-2">
<a href="/administrador/servicios/{{ $servicio->id }}/editar" class="btn btn-sm btn-outline-primary">Editar</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="6" class="text-center text-muted py-4">No hay servicios cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-12" id="seccion-asistente">
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Asistente Virtual (FAQ)</h2>
</div>
<div class="card-body border-bottom">
<div class="alert alert-info py-2" role="alert">
Para configurar textos fijos del asistente, usé estas intenciones: <strong>ui_nombre</strong> (nombre del asistente), <strong>ui_burbuja</strong> (burbuja flotante), <strong>ui_panel_inicio</strong> (primer mensaje del panel) y <strong>ui_error</strong> (mensaje cuando no encuentra respuesta). En esas intenciones, las palabras clave son opcionales. Los <strong>ui_chips</strong> ahora se administran en la sección separada de abajo.
</div>
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3">
@csrf
<div class="col-md-4">
<label for="faq_intencion" class="form-label">Intención (opcional)</label>
<input type="text" id="faq_intencion" name="intencion" class="form-control" value="{{ old('intencion') }}" maxlength="100" placeholder="Ej: turnos">
</div>
<div class="col-md-4">
<label for="faq_orden" class="form-label">Orden</label>
<input type="number" id="faq_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-4">
<label for="faq_activo" class="form-label">Estado</label>
<select id="faq_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12">
<label for="faq_palabras_clave" class="form-label">Palabras clave (separadas por coma, opcional para intenciones ui_*)</label>
<input type="text" id="faq_palabras_clave" name="palabras_clave" class="form-control" value="{{ old('palabras_clave') }}" placeholder="Ej: turno, consulta, formulario">
</div>
<div class="col-12">
<label for="faq_respuesta" class="form-label">Respuesta</label>
<textarea id="faq_respuesta" name="respuesta" class="form-control" rows="3" maxlength="5000" required>{{ old('respuesta') }}</textarea>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Agregar FAQ</button>
</div>
</form>
</div>
<div class="card-body border-bottom">
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="h6 mb-0">ui_chips (sugerencias rápidas)</h3>
</div>
<form action="/administrador/contenido-web/faqs" method="POST" class="row g-3 mb-3">
@csrf
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-7">
<label for="chip_respuesta" class="form-label">Texto del ui_chip</label>
<input type="text" id="chip_respuesta" name="respuesta" class="form-control" value="{{ old('respuesta') }}" maxlength="255" required placeholder="Ej: Quiero sacar un turno">
</div>
<div class="col-md-2">
<label for="chip_orden" class="form-label">Orden</label>
<input type="number" id="chip_orden" name="orden" class="form-control" value="{{ old('orden', 100) }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<label for="chip_activo" class="form-label">Estado</label>
<select id="chip_activo" name="activo" class="form-select" required>
<option value="1" {{ old('activo', '1') === '1' ? 'selected' : '' }}>Activo</option>
<option value="0" {{ old('activo') === '0' ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-md-1 d-flex align-items-end">
<button type="submit" class="btn btn-outline-primary w-100"></button>
</div>
</form>
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>ui_chip</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($chipsAsistenteAdmin as $chip)
<tr>
<td>{{ $chip->id }}</td>
<td colspan="3">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<input type="hidden" name="intencion" value="ui_chips">
<input type="hidden" name="palabras_clave" value="">
<div class="col-md-8">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $chip->respuesta }}" maxlength="255" required>
</div>
<div class="col-md-2">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $chip->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-2">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $chip->activo ? 'selected' : '' }}>Activo</option>
<option value="0" {{ !$chip->activo ? 'selected' : '' }}>Inactivo</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $chip->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar este ui_chip?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="5" class="text-center text-muted py-4">No hay ui_chips cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Intención</th>
<th>Palabras clave</th>
<th>Respuesta</th>
<th>Orden</th>
<th>Estado</th>
<th class="text-end">Acciones</th>
</tr>
</thead>
<tbody>
@forelse($faqsAsistente as $faq)
<tr>
<td>{{ $faq->id }}</td>
<td colspan="5">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" class="row g-2">
@csrf
@method('PUT')
<div class="col-md-3">
<input type="text" name="intencion" class="form-control form-control-sm" value="{{ $faq->intencion }}" maxlength="100" placeholder="Intención">
</div>
<div class="col-md-3">
<input type="text" name="palabras_clave" class="form-control form-control-sm" value="{{ implode(', ', $faq->palabras_clave ?? []) }}" placeholder="Palabras clave">
</div>
<div class="col-md-4">
<input type="text" name="respuesta" class="form-control form-control-sm" value="{{ $faq->respuesta }}" maxlength="5000" required>
</div>
<div class="col-md-1">
<input type="number" name="orden" class="form-control form-control-sm" value="{{ $faq->orden }}" min="0" max="65535" required>
</div>
<div class="col-md-1">
<select name="activo" class="form-select form-select-sm" required>
<option value="1" {{ $faq->activo ? 'selected' : '' }}>Act.</option>
<option value="0" {{ !$faq->activo ? 'selected' : '' }}>Inact.</option>
</select>
</div>
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-sm btn-outline-primary">Guardar</button>
</div>
</form>
</td>
<td class="text-end">
<form action="/administrador/contenido-web/faqs/{{ $faq->id }}" method="POST" onsubmit="return confirm('¿Confirmás borrar esta FAQ?');">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-sm btn-outline-danger">Borrar</button>
</form>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay FAQs cargadas para el asistente.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,142 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nueva Profesión - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-7">
<h1 class="h4 mb-3">Nueva profesión</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/profesiones" class="row g-3">
@csrf
<div class="col-12">
<label for="titulo" class="form-label">Título</label>
<input id="titulo" name="titulo" type="text" class="form-control @error('titulo') is-invalid @enderror" value="{{ old('titulo') }}" required autofocus>
@error('titulo')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12">
<label for="visible_en_formulario" class="form-label">¿Visible en el formulario de turnos?</label>
<select id="visible_en_formulario" name="visible_en_formulario" class="form-select" required>
<option value="1" @selected(old('visible_en_formulario', '1') === '1')></option>
<option value="0" @selected(old('visible_en_formulario') === '0')>No</option>
</select>
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/contenido/profesiones" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Crear profesión</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,181 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nuevo Servicio - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-9">
<h1 class="h4 mb-3">Nuevo servicio</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/servicios" class="row g-3" enctype="multipart/form-data">
@csrf
<div class="col-12">
<label for="titulo" class="form-label">Título</label>
<input id="titulo" name="titulo" type="text" class="form-control @error('titulo') is-invalid @enderror" value="{{ old('titulo') }}" required autofocus>
@error('titulo')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12">
<label for="profesion_id" class="form-label">Profesión</label>
<select id="profesion_id" name="profesion_id" class="form-select @error('profesion_id') is-invalid @enderror" required>
<option value="">Seleccionar profesión</option>
@foreach($profesiones as $profesion)
<option value="{{ $profesion->id }}" @selected((string) old('profesion_id') === (string) $profesion->id)>
{{ $profesion->titulo }}
</option>
@endforeach
</select>
@error('profesion_id')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12">
<label for="estado" class="form-label">Estado</label>
<select id="estado" name="estado" class="form-select" required>
<option value="activo" @selected(old('estado', 'activo') === 'activo')>Activo</option>
<option value="inactivo" @selected(old('estado') === 'inactivo')>Inactivo</option>
</select>
</div>
<div class="col-12">
<label for="visibleenweb" class="form-label">Visible en web</label>
<select id="visibleenweb" name="visibleenweb" class="form-select" required>
<option value="si" @selected(old('visibleenweb', 'si') === 'si')>Si</option>
<option value="no" @selected(old('visibleenweb') === 'no')>No</option>
</select>
</div>
<div class="col-12">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control @error('descripcion') is-invalid @enderror" rows="4" required>{{ old('descripcion') }}</textarea>
@error('descripcion')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12">
<label for="foto" class="form-label">Foto del servicio <span class="text-muted">(opcional)</span></label>
<input id="foto" name="foto" type="file" class="form-control @error('foto') is-invalid @enderror" accept="image/jpeg,image/png,image/webp">
@error('foto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/contenido/servicios" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Crear servicio</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,112 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="#top">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/bugs">Bugs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main>
<div class="container py-5">
<h1 class="h3 mb-4 text-center">
¡Bienvenida administradora!
</h1>
<p class="text-muted mb-4 text-center">Desde acá podés gestionar profesionales, revisar logs y actualizar el contenido del sitio web.</p>
<div class="d-flex justify-content-center mb-4">
<a href="/administrador/perfil" class="btn btn-outline-primary">Editar mis datos</a>
</div>
@if (session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if (session('admin_action_error'))
<div class="alert alert-danger" role="alert">
{{ session('admin_action_error') }}
</div>
@endif
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,143 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Editar Profesión - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-7">
<h1 class="h4 mb-3">Editar profesión</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/profesiones/{{ $profesion->id }}" class="row g-3">
@csrf
@method('PUT')
<div class="col-12">
<label for="titulo" class="form-label">Título</label>
<input id="titulo" name="titulo" type="text" class="form-control @error('titulo') is-invalid @enderror" value="{{ old('titulo', $profesion->titulo) }}" required>
@error('titulo')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12">
<label for="visible_en_formulario" class="form-label">¿Visible en el formulario de turnos?</label>
<select id="visible_en_formulario" name="visible_en_formulario" class="form-select" required>
<option value="1" @selected((string) old('visible_en_formulario', (int) $profesion->visible_en_formulario) === '1')></option>
<option value="0" @selected((string) old('visible_en_formulario', (int) $profesion->visible_en_formulario) === '0')>No</option>
</select>
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/contenido/profesiones" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,280 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Editar Profesional</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="#menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-9">
<h1 class="h4 mb-3">Editar profesional</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/profesionales/{{ $profesional->id }}" class="row g-3" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="col-12">
<label for="dni" class="form-label">DNI</label>
<input id="dni" name="dni" type="text" class="form-control" value="{{ old('dni', $profesional->dni) }}" pattern="[0-9A-Za-z]{7,20}" maxlength="20" title="Ingrese entre 7 y 20 caracteres alfanuméricos." required>
<div class="form-text">Se permiten letras y números.</div>
</div>
<div class="col-12">
<label for="nombre" class="form-label">Nombre</label>
<input id="nombre" name="nombre" type="text" class="form-control" value="{{ old('nombre', $profesional->persona?->nombre ?? '') }}" required>
</div>
<div class="col-12">
<label for="apellido" class="form-label">Apellido</label>
<input id="apellido" name="apellido" type="text" class="form-control" value="{{ old('apellido', $profesional->persona?->apellido ?? '') }}" required>
</div>
<div class="col-12">
<label for="cuil" class="form-label">CUIL</label>
<input id="cuil" name="cuil" type="text" class="form-control" value="{{ old('cuil', $profesional->persona?->cuil ?? '') }}" required>
</div>
<div class="col-12">
<label for="fechanac" class="form-label">Fecha de nacimiento</label>
<input id="fechanac" name="fechanac" type="date" class="form-control" value="{{ old('fechanac', $profesional->persona?->fechanac ? \Illuminate\Support\Carbon::parse($profesional->persona->fechanac)->format('Y-m-d') : '') }}" required>
</div>
<div class="col-12">
<label class="form-label">Usuario credencial</label>
<input type="text" class="form-control border-danger bg-light" value="{{ $profesional->credencialProfesional?->usuario ?? '' }}" readonly>
<div class="form-text text-danger">No editable</div>
</div>
<div class="col-12">
<label class="form-label">Rol credencial</label>
<input type="text" class="form-control border-danger bg-light" value="{{ $profesional->credencialProfesional?->rol ?? '' }}" readonly>
<div class="form-text text-danger">No editable</div>
</div>
<div class="col-12">
<label class="form-label">Estado baja</label>
<input type="text" class="form-control border-danger bg-light" value="{{ (int) $profesional->baja_id === 1 ? 'Activo' : 'De baja' }}" readonly>
<div class="form-text text-danger">No editable</div>
</div>
<div class="col-12">
<label for="correo" class="form-label">Correo</label>
<input id="correo" name="correo" type="email" class="form-control" value="{{ old('correo', $profesional->correo) }}" required>
</div>
<div class="col-12">
<label for="foto" class="form-label">Foto del profesional</label>
<input type="hidden" id="quitar_foto" name="quitar_foto" value="{{ old('quitar_foto', '0') }}">
@if($profesional->persona?->Foto)
<div class="mb-2">
<img src="{{ asset($profesional->persona->Foto->ruta) }}" alt="Foto actual" class="img-thumbnail" style="max-height: 180px; object-fit: cover;">
<p class="form-text">Foto actual. Si subís una nueva, reemplazará la actual.</p>
</div>
@else
<p class="form-text text-muted">Este profesional no tiene foto cargada.</p>
@endif
<input id="foto" name="foto" type="file" class="form-control @error('foto') is-invalid @enderror" accept="image/jpeg,image/png,image/webp">
@error('foto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<div class="mt-2 d-flex align-items-center gap-2">
<button type="button" id="btn-quitar-foto" class="btn btn-sm btn-outline-danger">Quitar foto</button>
<span id="quitar-foto-estado" class="small text-muted"></span>
</div>
</div>
<div class="col-12">
<label for="matricula" class="form-label">Matrícula</label>
<input id="matricula" name="matricula" type="text" class="form-control" value="{{ old('matricula', $profesional->matricula) }}" required>
</div>
<div class="col-12">
<label for="profesion_id" class="form-label">Profesión</label>
<input type="hidden" id="profesion_id" name="profesion_id" value="{{ (string) $profesional->profesion_id }}">
<input type="text" class="form-control border-danger bg-light" value="{{ $profesional->profesion?->titulo ?? '-' }}" readonly>
<div class="form-text text-danger">No editable</div>
</div>
<div class="col-12">
<label class="form-label d-block">Servicios que ofrece</label>
@php
$serviciosSeleccionadosFinal = array_map(
'strval',
old('servicio_ids', array_map('strval', $serviciosSeleccionados ?? []))
);
@endphp
<div id="servicios-container" class="d-flex flex-wrap gap-3 border rounded p-3 bg-light">
@foreach($servicios as $servicio)
<div class="form-check" data-profesion-id="{{ $servicio->profesion_id }}">
<input
class="form-check-input"
type="checkbox"
id="servicio_{{ $servicio->id }}"
name="servicio_ids[]"
value="{{ $servicio->id }}"
@checked(in_array((string) $servicio->id, $serviciosSeleccionadosFinal, true))
>
<label class="form-check-label" for="servicio_{{ $servicio->id }}">{{ $servicio->titulo }}</label>
</div>
@endforeach
</div>
<div class="form-text">Solo se muestran servicios de la profesión seleccionada.</div>
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/profesionales" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
<script>
(function () {
const serviciosContainer = document.getElementById('servicios-container');
const itemsServicio = Array.from(serviciosContainer.querySelectorAll('[data-profesion-id]'));
const profesionSelect = document.getElementById('profesion_id');
const quitarFotoInput = document.getElementById('quitar_foto');
const quitarFotoBtn = document.getElementById('btn-quitar-foto');
const quitarFotoEstado = document.getElementById('quitar-foto-estado');
const filtrarServicios = () => {
const profesionId = profesionSelect?.value ?? '';
itemsServicio.forEach((item) => {
const checkbox = item.querySelector('input[type="checkbox"]');
const visible = item.dataset.profesionId === profesionId;
item.hidden = !visible;
if (checkbox) {
checkbox.disabled = !visible;
if (!visible) checkbox.checked = false;
}
});
};
filtrarServicios();
profesionSelect?.addEventListener('change', filtrarServicios);
if (quitarFotoInput && quitarFotoBtn && quitarFotoEstado) {
const syncQuitarFoto = () => {
const activo = quitarFotoInput.value === '1';
quitarFotoBtn.classList.toggle('btn-danger', activo);
quitarFotoBtn.classList.toggle('btn-outline-danger', !activo);
quitarFotoEstado.textContent = activo ? 'Se quitará la foto al guardar.' : '';
};
quitarFotoBtn.addEventListener('click', () => {
quitarFotoInput.value = quitarFotoInput.value === '1' ? '0' : '1';
syncQuitarFoto();
});
syncQuitarFoto();
}
})();
</script>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,181 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Editar Servicio - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-9">
<h1 class="h4 mb-3">Editar servicio</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/servicios/{{ $servicio->id }}" class="row g-3" enctype="multipart/form-data">
@csrf
@method('PUT')
<div class="col-12 col-md-6">
<label for="titulo" class="form-label">Título</label>
<input id="titulo" name="titulo" type="text" class="form-control" value="{{ old('titulo', $servicio->titulo) }}" required>
</div>
<div class="col-12 col-md-6">
<label for="estado" class="form-label">Estado</label>
<select id="estado" name="estado" class="form-select" required>
<option value="activo" @selected(old('estado', $servicio->estado) === 'activo')>Activo</option>
<option value="inactivo" @selected(old('estado', $servicio->estado) === 'inactivo')>Inactivo</option>
</select>
</div>
<div class="col-12 col-md-6">
<label for="visibleenweb" class="form-label">Visible en web</label>
<select id="visibleenweb" name="visibleenweb" class="form-select" required>
<option value="si" @selected(old('visibleenweb', $servicio->visibleenweb ?? 'si') === 'si')>Si</option>
<option value="no" @selected(old('visibleenweb', $servicio->visibleenweb) === 'no')>No</option>
</select>
</div>
<div class="col-12 col-md-6">
<label for="profesion_id" class="form-label">Profesión</label>
<select id="profesion_id" name="profesion_id" class="form-select" required>
<option value="">Seleccionar profesión</option>
@foreach($profesiones as $profesion)
<option value="{{ $profesion->id }}" @selected((string) old('profesion_id', $servicio->profesion_id) === (string) $profesion->id)>
{{ $profesion->titulo }}
</option>
@endforeach
</select>
</div>
<div class="col-12">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="4" required>{{ old('descripcion', $servicio->descripcion) }}</textarea>
</div>
<div class="col-12">
<label for="foto" class="form-label">Foto del servicio</label>
@if($servicio->foto)
<div class="mb-2">
<img src="{{ asset($servicio->foto->ruta) }}" alt="Foto actual" class="img-thumbnail" style="max-height: 180px; object-fit: cover;">
<p class="form-text">Foto actual. Si subís una nueva, reemplazará la actual.</p>
</div>
@else
<p class="form-text text-muted">Este servicio no tiene foto cargada.</p>
@endif
<input id="foto" name="foto" type="file" class="form-control @error('foto') is-invalid @enderror" accept="image/jpeg,image/png,image/webp">
@error('foto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/contenido/servicios" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,165 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Emails - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">Hola, Administrador</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Gestion de Emails</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-header bg-white">
<h2 class="h5 mb-0">Mensajes de email para turnos</h2>
</div>
<div class="card-body">
<form action="/administrador/emails" method="POST" class="row g-4">
@csrf
@foreach($tipos as $tipo => $config)
@php
$item = $notificaciones->get($tipo);
$mensajeInicio = old($tipo . '_mensaje_inicio', $item?->mensaje_inicio ?? $config['mensaje_inicio']);
$mensajeFinal = old($tipo . '_mensaje_final', $item?->mensaje_final ?? $config['mensaje_final']);
@endphp
<div class="col-12">
<div class="border rounded p-3 bg-white">
<h3 class="h6 mb-3">{{ $config['titulo'] }}</h3>
<div class="row g-3">
<div class="col-12">
<label for="{{ $tipo }}_mensaje_inicio" class="form-label">Mensaje de inicio</label>
<textarea
id="{{ $tipo }}_mensaje_inicio"
name="{{ $tipo }}_mensaje_inicio"
class="form-control"
rows="3"
maxlength="5000"
required
>{{ $mensajeInicio }}</textarea>
</div>
<div class="col-12">
<label for="{{ $tipo }}_mensaje_final" class="form-label">Mensaje final</label>
<textarea
id="{{ $tipo }}_mensaje_final"
name="{{ $tipo }}_mensaje_final"
class="form-control"
rows="3"
maxlength="5000"
required
>{{ $mensajeFinal }}</textarea>
</div>
</div>
</div>
</div>
@endforeach
<div class="col-12 d-flex justify-content-end">
<button type="submit" class="btn btn-primary">Guardar cambios</button>
</div>
</form>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,217 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fallas - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/bugs">Bugs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Fallas del sistema</h1>
<a href="/administrador/dashboard" class="btn btn-outline-secondary btn-sm">Volver</a>
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<form method="GET" action="/administrador/fallas" class="row g-3 align-items-end">
<div class="col-12 col-md-3">
<label for="codigo" class="form-label">Codigo</label>
<select id="codigo" name="codigo" class="form-select">
<option value="">Todos</option>
@foreach($codigosDisponibles as $codigo)
<option value="{{ $codigo }}" @selected(request('codigo') === $codigo)>{{ $codigo }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-2">
<label for="desde" class="form-label">Desde</label>
<input id="desde" name="desde" type="date" class="form-control" value="{{ request('desde') }}">
</div>
<div class="col-12 col-md-2">
<label for="hasta" class="form-label">Hasta</label>
<input id="hasta" name="hasta" type="date" class="form-control" value="{{ request('hasta') }}">
</div>
<div class="col-12 col-md-3">
<label for="q" class="form-label">Texto</label>
<input id="q" name="q" type="text" class="form-control" value="{{ request('q') }}" placeholder="Mensaje, URL o trace...">
</div>
<div class="col-12 col-md-2 d-flex gap-2">
<button type="submit" class="btn btn-primary w-100">Filtrar</button>
<a href="/administrador/fallas" class="btn btn-outline-secondary w-100">Limpiar</a>
</div>
</form>
</div>
</div>
<div class="card border shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th style="width: 80px;">ID</th>
<th style="width: 170px;">Fecha y hora</th>
<th style="width: 160px;">Codigo</th>
<th style="width: 260px;">Mensaje</th>
<th style="width: 240px;">URL</th>
<th style="width: 320px;">Trace</th>
<th style="width: 120px;">Accion</th>
</tr>
</thead>
<tbody>
@forelse($fallas as $falla)
@php
$fechaFalla = $falla->fecha_hora ? \Illuminate\Support\Carbon::parse($falla->fecha_hora)->format('d/m/Y H:i:s') : '-';
@endphp
<tr>
<td>{{ $falla->id }}</td>
<td>{{ $fechaFalla }}</td>
<td>{{ $falla->codigo ?: '-' }}</td>
<td style="white-space: pre-line;">{{ \Illuminate\Support\Str::limit((string) $falla->mensaje, 180) }}</td>
<td>{{ \Illuminate\Support\Str::limit((string) $falla->url, 100) }}</td>
<td style="white-space: pre-line; min-width: 320px;">{{ \Illuminate\Support\Str::limit((string) $falla->track_trace, 320) }}</td>
<td>
<button
type="button"
class="btn btn-outline-primary btn-sm"
data-bs-toggle="modal"
data-bs-target="#modalFalla{{ $falla->id }}"
>
Ver detalle
</button>
<div class="modal fade" id="modalFalla{{ $falla->id }}" tabindex="-1" aria-labelledby="modalFalla{{ $falla->id }}Label" aria-hidden="true">
<div class="modal-dialog modal-xl modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="modalFalla{{ $falla->id }}Label">Detalle de falla #{{ $falla->id }}</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<strong>Fecha y hora:</strong>
{{ $fechaFalla }}
</div>
<div class="mb-3">
<strong>Codigo:</strong>
{{ $falla->codigo ?: '-' }}
</div>
<div class="mb-3">
<strong>URL:</strong>
<div class="text-break">{{ $falla->url ?: '-' }}</div>
</div>
<div class="mb-3">
<strong>Mensaje:</strong>
<pre class="mb-0 p-3 bg-light border rounded" style="white-space: pre-wrap; word-break: break-word;">{{ (string) $falla->mensaje }}</pre>
</div>
<div>
<strong>Trace completo:</strong>
<pre class="mb-0 p-3 bg-light border rounded" style="white-space: pre-wrap; word-break: break-word;">{{ (string) $falla->track_trace }}</pre>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cerrar</button>
</div>
</div>
</div>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay fallas registradas.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@if($fallas->hasPages())
<div class="d-flex justify-content-center mt-3">
{{ $fallas->links() }}
</div>
@endif
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,82 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<title>Logs de Seguridad</title>
<style>
body {
font-family: DejaVu Sans, Arial, sans-serif;
font-size: 11px;
color: #111;
}
h1 {
font-size: 16px;
margin: 0 0 8px 0;
}
.meta {
margin-bottom: 10px;
color: #444;
}
table {
width: 100%;
border-collapse: collapse;
}
th,
td {
border: 1px solid #d1d5db;
padding: 6px;
vertical-align: top;
text-align: left;
}
th {
background: #f3f4f6;
font-weight: bold;
}
</style>
</head>
<body>
<h1>Logs de seguridad</h1>
<div class="meta">Generado el {{ now()->format('d/m/Y H:i:s') }} - Total de registros: {{ $logs->count() }}</div>
<table>
<thead>
<tr>
<th style="width: 6%;">ID</th>
<th style="width: 12%;">Fecha y hora</th>
<th style="width: 12%;">Acción</th>
<th style="width: 8%;">Rol</th>
<th style="width: 12%;">IP origen</th>
<th style="width: 18%;">Responsable</th>
<th style="width: 32%;">Descripción</th>
</tr>
</thead>
<tbody>
@forelse($logs as $log)
@php
$fechaLog = $log->fechahora ? \Illuminate\Support\Carbon::parse($log->fechahora)->format('d/m/Y H:i:s') : '-';
$responsable = trim(($log->responsable?->nombre ?? '') . ' ' . ($log->responsable?->apellido ?? '')) ?: '-';
@endphp
<tr>
<td>{{ $log->id }}</td>
<td>{{ $fechaLog }}</td>
<td>{{ $log->accion?->descripcion ?? '-' }}</td>
<td>{{ $log->rol }}</td>
<td>{{ $log->IPorigen }}</td>
<td>{{ $responsable }}</td>
<td>{{ $log->descripcion }}</td>
</tr>
@empty
<tr>
<td colspan="7">No hay logs registrados.</td>
</tr>
@endforelse
</tbody>
</table>
</body>
</html>
@@ -0,0 +1,191 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Logs - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="d-flex justify-content-between align-items-center mb-3">
<h1 class="h4 mb-0">Logs</h1>
<div class="d-flex gap-2">
<a href="{{ url('/administrador/logs/exportar-json') . (request()->getQueryString() ? '?' . request()->getQueryString() : '') }}" class="btn btn-success btn-sm">Exportar a JSON</a>
<a href="/administrador/dashboard" class="btn btn-outline-secondary btn-sm">Volver</a>
</div>
</div>
<div class="alert alert-warning" role="alert">
Los logs tendrán una duración de 3 meses, luego serán eliminados automáticamente.
</div>
<div class="card border shadow-sm mb-3">
<div class="card-body">
<form method="GET" action="/administrador/logs" class="row g-3 align-items-end">
<div class="col-12 col-md-3">
<label for="accion" class="form-label">Acción</label>
<select id="accion" name="accion" class="form-select">
<option value="">Todas</option>
@foreach($accionesDisponibles as $accion)
<option value="{{ $accion }}" @selected(request('accion') === $accion)>{{ $accion }}</option>
@endforeach
</select>
</div>
<div class="col-12 col-md-2">
<label for="desde" class="form-label">Desde</label>
<input id="desde" name="desde" type="date" class="form-control" value="{{ request('desde') }}">
</div>
<div class="col-12 col-md-2">
<label for="hasta" class="form-label">Hasta</label>
<input id="hasta" name="hasta" type="date" class="form-control" value="{{ request('hasta') }}">
</div>
<div class="col-12 col-md-3">
<label for="q" class="form-label">Texto</label>
<input id="q" name="q" type="text" class="form-control" value="{{ request('q') }}" placeholder="Descripción, IP, rol, persona...">
</div>
<div class="col-12 col-md-2 d-flex gap-2">
<button type="submit" class="btn btn-primary w-100">Filtrar</button>
<a href="/administrador/logs" class="btn btn-outline-secondary w-100">Limpiar</a>
</div>
</form>
</div>
</div>
<div class="card border shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th style="width: 90px;">ID</th>
<th style="width: 170px;">Fecha y hora</th>
<th style="width: 150px;">ID accion</th>
<th style="width: 120px;">Rol</th>
<th style="width: 160px;">IP origen</th>
<th style="width: 220px;">Responsable</th>
<th>Descripcion</th>
</tr>
</thead>
<tbody>
@forelse($logs as $log)
@php
$fechaLog = $log->fechahora ? \Illuminate\Support\Carbon::parse($log->fechahora)->format('d/m/Y H:i:s') : '-';
@endphp
<tr>
<td>{{ $log->id }}</td>
<td>{{ $fechaLog }}</td>
<td>{{ $log->accion_id ?? '-' }}</td>
<td>{{ $log->rol }}</td>
<td>{{ $log->IPorigen }}</td>
<td>{{ $log->responsable_nombre ?: '-' }}</td>
<td>{{ $log->descripcion }}</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay logs registrados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@if($logs->hasPages())
<div class="d-flex justify-content-center mt-3">
{{ $logs->links() }}
</div>
@endif
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,441 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nuevo Profesional</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li> <li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li> </ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesión</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-9">
<h1 class="h4 mb-3">Agregar nuevo profesional</h1>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form method="POST" action="/administrador/profesionales" enctype="multipart/form-data" class="row g-3">
@csrf
{{-- DNI con botón de verificación --}}
<div class="col-12 col-md-6">
<label for="dni" class="form-label">DNI</label>
<div class="input-group">
<input id="dni" name="dni" type="text" class="form-control" value="{{ old('dni') }}" pattern="[0-9A-Za-z]{7,20}" maxlength="20" title="Nmeros y letras: 7 a 20 caracteres" required>
<button type="button" class="btn btn-outline-secondary" id="btn-verificar-dni">Verificar</button>
</div>
</div>
{{-- Espacio para el mensaje de estado de la búsqueda --}}
<div class="col-12" id="dni-estado" @if(!old('dni')) style="display:none" @endif>
@if(old('persona_existe') === '1')
<div class="alert alert-info mb-0">
<strong>Persona ya registrada en el sistema.</strong> Complete solo los datos faltantes para registrar a este profesional (foto, correo, matrícula, profesión, servicios y contraseña).
</div>
@elseif(old('dni'))
<div class="alert alert-warning mb-0">
<strong>Persona no encontrada.</strong> Complete todos los datos para registrar a esta persona.
</div>
@endif
</div>
{{-- Input oculto que indica si la persona ya existía --}}
<input type="hidden" id="persona_existe" name="persona_existe" value="{{ old('persona_existe', '0') }}">
<input type="hidden" id="dni_verificado" name="dni_verificado" value="{{ old('dni_verificado', '') }}">
{{-- Campos básicos de la persona --}}
<div class="col-12 col-md-6" data-campo-persona>
<label for="nombre" class="form-label">Nombre</label>
<input id="nombre" name="nombre" type="text" class="form-control {{ old('persona_existe') === '1' ? 'bg-light' : '' }}" value="{{ old('nombre') }}" pattern="[A-Za-z\s]+" oninput="this.value=this.value.replace(/[^A-Za-z\s]/g,'')" {{ old('persona_existe') !== '1' ? 'required' : 'readonly' }}>
</div>
<div class="col-12 col-md-6" data-campo-persona>
<label for="apellido" class="form-label">Apellido</label>
<input id="apellido" name="apellido" type="text" class="form-control {{ old('persona_existe') === '1' ? 'bg-light' : '' }}" value="{{ old('apellido') }}" pattern="[A-Za-z\s]+" oninput="this.value=this.value.replace(/[^A-Za-z\s]/g,'')" {{ old('persona_existe') !== '1' ? 'required' : 'readonly' }}>
</div>
<div class="col-12 col-md-6" data-campo-persona>
<label for="cuil" class="form-label">CUIL</label>
<input id="cuil" name="cuil" type="text" class="form-control {{ old('persona_existe') === '1' ? 'bg-light' : '' }}" value="{{ old('cuil') }}" inputmode="numeric" pattern="[0-9]+" oninput="this.value=this.value.replace(/\D/g,'')" {{ old('persona_existe') !== '1' ? 'required' : 'readonly' }}>
</div>
<div class="col-12 col-md-6" data-campo-persona>
<label for="celular" class="form-label">Celular</label>
<input id="celular" name="celular" type="text" class="form-control" value="{{ old('celular') }}" inputmode="numeric" pattern="[0-9]+" oninput="this.value=this.value.replace(/\D/g,'')" required>
</div>
<div class="col-12 col-md-6" data-campo-persona>
<label for="fechanac" class="form-label">Fecha de nacimiento</label>
<input id="fechanac" name="fechanac" type="date" class="form-control {{ old('persona_existe') === '1' ? 'bg-light' : '' }}" value="{{ old('fechanac') }}" {{ old('persona_existe') !== '1' ? 'required' : 'readonly' }}>
</div>
<div class="col-12 col-md-6" id="foto-section">
<label for="foto" class="form-label">Foto</label>
<input id="foto" name="foto" type="file" class="form-control" accept=".jpg,.jpeg,.png,.webp">
<div class="form-text">Opcional. Formatos permitidos: JPG, PNG, WEBP.</div>
</div>
{{-- Correo y matrícula siempre requeridos --}}
<div class="col-12 col-md-6">
<label for="correo" class="form-label">Correo</label>
<input id="correo" name="correo" type="email" class="form-control" value="{{ old('correo') }}" required>
</div>
<div class="col-12 col-md-6">
<label for="matricula" class="form-label">Matrícula</label>
<input id="matricula" name="matricula" type="text" class="form-control" value="{{ old('matricula') }}" required>
</div>
<div class="col-12 col-md-6">
<label for="profesion_id" class="form-label">Profesión</label>
<select id="profesion_id" name="profesion_id" class="form-select" required>
<option value="">Seleccionar profesión</option>
@foreach($profesiones as $profesion)
<option value="{{ $profesion->id }}" @selected(old('profesion_id') == $profesion->id)>
{{ $profesion->titulo }}
</option>
@endforeach
</select>
</div>
{{-- Usuario autogenerado (solo lectura) --}}
<div class="col-12 col-md-6">
<label class="form-label">Usuario (autogenerado)</label>
<input id="usuario-preview" type="text" class="form-control bg-light" readonly
value="{{ old('dni') && old('profesion_id') ? old('dni').'-'.old('profesion_id') : '' }}"
placeholder="Se calcula al ingresar DNI y Profesión">
<div class="form-text">Formato: <strong>DNI-CódigoProfesión</strong>. Ej.: 43293244-1</div>
</div>
<div class="col-12">
<label class="form-label d-block">Servicios que ofrece</label>
<div id="servicios-container" class="d-flex flex-wrap gap-3 border rounded p-3 bg-light">
@foreach($servicios as $servicio)
<div class="form-check" data-profesion-id="{{ $servicio->profesion_id }}">
<input
class="form-check-input"
type="checkbox"
id="servicio_{{ $servicio->id }}"
name="servicio_ids[]"
value="{{ $servicio->id }}"
@checked(in_array((string) $servicio->id, array_map('strval', old('servicio_ids', [])), true))
>
<label class="form-check-label" for="servicio_{{ $servicio->id }}">{{ $servicio->titulo }}</label>
</div>
@endforeach
</div>
<div class="form-text">Primero selecciona una profesión para ver sus servicios.</div>
</div>
<div class="col-12 col-md-6">
<label for="contra" class="form-label">Contraseña</label>
<input id="contra" name="contra" type="password" class="form-control" minlength="6" required>
</div>
<div class="col-12 d-flex gap-2 justify-content-end mt-2">
<a href="/administrador/profesionales" class="btn btn-outline-secondary">Cancelar</a>
<button type="submit" class="btn btn-primary">Guardar profesional</button>
</div>
</form>
</div>
</div>
</div>
</div>
</main>
<script>
(function () {
const dniInput = document.getElementById('dni');
const celularInput = document.getElementById('celular');
const btnVerificar = document.getElementById('btn-verificar-dni');
const dniEstado = document.getElementById('dni-estado');
const personaExisteInput = document.getElementById('persona_existe');
const dniVerificadoInput = document.getElementById('dni_verificado');
const usuarioPreview = document.getElementById('usuario-preview');
const profesionSelect = document.getElementById('profesion_id');
const serviciosContainer = document.getElementById('servicios-container');
const formulario = document.querySelector('form[action="/administrador/profesionales"]');
const controlesBloqueables = formulario
? Array.from(formulario.querySelectorAll('input, select, textarea, button'))
: [];
/* --- Preview del usuario autogenerado --- */
const actualizarUsuario = () => {
const dni = dniInput.value.trim();
const profesionId = profesionSelect.value;
usuarioPreview.value = (dni && profesionId) ? `${dni}-${profesionId}` : '';
};
const dniEstaVerificado = () => {
const dniActual = dniInput.value.trim();
return dniActual !== '' && dniVerificadoInput.value === dniActual;
};
const aplicarBloqueoVerificacion = () => {
const verificado = dniEstaVerificado();
controlesBloqueables.forEach((control) => {
const esPermitido = control.id === 'dni'
|| control.id === 'btn-verificar-dni'
|| control.id === 'persona_existe'
|| control.id === 'dni_verificado'
|| control.type === 'hidden'
|| control.name === '_token'
|| control.name === '_method';
if (esPermitido) {
return;
}
control.disabled = !verificado;
});
};
dniInput.addEventListener('input', () => {
dniInput.value = dniInput.value.replace(/[^0-9A-Za-z]/g, '');
actualizarUsuario();
personaExisteInput.value = '0';
dniVerificadoInput.value = '';
aplicarBloqueoVerificacion();
filtrarServicios();
});
if (celularInput) {
celularInput.addEventListener('input', () => {
celularInput.value = celularInput.value.replace(/\D/g, '');
});
}
profesionSelect.addEventListener('change', actualizarUsuario);
/* --- Filtro de servicios por profesin --- */
const itemsServicio = Array.from(serviciosContainer.querySelectorAll('[data-profesion-id]'));
const filtrarServicios = () => {
const profesionId = profesionSelect.value;
const verificado = dniEstaVerificado();
itemsServicio.forEach((item) => {
const checkbox = item.querySelector('input[type="checkbox"]');
const visible = verificado && Boolean(profesionId) && item.dataset.profesionId === profesionId;
item.hidden = !visible;
if (checkbox) {
checkbox.disabled = !visible;
if (!visible) checkbox.checked = false;
}
});
};
filtrarServicios();
profesionSelect.addEventListener('change', filtrarServicios);
/* --- Helpers para aplicar el estado encontrada/no-encontrada --- */
const aplicarPersonaEncontrada = (persona) => {
personaExisteInput.value = '1';
dniVerificadoInput.value = dniInput.value.trim();
aplicarBloqueoVerificacion();
dniEstado.innerHTML = `<div class="alert alert-info mb-0">
<strong>Persona ya registrada en el sistema.</strong>
Complete solo los datos faltantes para registrar a este profesional (foto, correo, matrícula, profesión, servicios y contraseña).
</div>`;
dniEstado.style.display = '';
document.getElementById('nombre').value = persona.nombre;
document.getElementById('apellido').value = persona.apellido;
document.getElementById('cuil').value = persona.cuil;
document.getElementById('celular').value = persona.celular ?? '';
document.getElementById('fechanac').value = persona.fechanac ?? '';
['nombre', 'apellido', 'cuil', 'fechanac'].forEach((id) => {
const el = document.getElementById(id);
el.setAttribute('readonly', true);
el.removeAttribute('required');
el.classList.add('bg-light');
});
actualizarUsuario();
filtrarServicios();
};
const aplicarPersonaNoEncontrada = (limpiar = true) => {
personaExisteInput.value = '0';
dniVerificadoInput.value = dniInput.value.trim();
aplicarBloqueoVerificacion();
dniEstado.innerHTML = `<div class="alert alert-warning mb-0">
<strong>Persona no encontrada.</strong>
Complete todos los datos para registrar a esta persona.
</div>`;
dniEstado.style.display = '';
['nombre', 'apellido', 'cuil', 'fechanac'].forEach((id) => {
const el = document.getElementById(id);
el.removeAttribute('disabled');
el.removeAttribute('readonly');
el.setAttribute('required', true);
el.classList.remove('bg-light');
if (limpiar) el.value = '';
});
actualizarUsuario();
filtrarServicios();
};
/* --- Verificacin contra el backend --- */
const verificarDni = async () => {
const dni = dniInput.value.trim();
if (dni.length < 3) return;
btnVerificar.disabled = true;
btnVerificar.textContent = '...';
try {
const res = await fetch(`/administrador/profesionales/buscar-persona?dni=${encodeURIComponent(dni)}`);
const data = await res.json();
if (data.encontrada) {
aplicarPersonaEncontrada(data.persona);
} else {
aplicarPersonaNoEncontrada();
}
} catch (err) {
console.error('Error al verificar DNI:', err);
} finally {
btnVerificar.disabled = false;
btnVerificar.textContent = 'Verificar';
}
};
btnVerificar.addEventListener('click', verificarDni);
dniInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') { e.preventDefault(); verificarDni(); }
});
/* Si al cargar la pgina ya hay un valor en DNI (re-submit con errores),
y persona_existe ya era 1, restauramos el estado visual */
const existeAlCargar = personaExisteInput.value === '1';
aplicarBloqueoVerificacion();
if (dniEstaVerificado() && dniInput.value.trim()) {
actualizarUsuario();
filtrarServicios();
}
if (existeAlCargar && dniEstaVerificado()) {
['nombre', 'apellido', 'cuil', 'fechanac'].forEach((id) => {
const el = document.getElementById(id);
el.removeAttribute('disabled');
el.setAttribute('readonly', true);
el.removeAttribute('required');
el.classList.add('bg-light');
});
}
formulario?.addEventListener('submit', (e) => {
const dniActual = dniInput.value.trim();
if (!dniActual || dniVerificadoInput.value !== dniActual) {
e.preventDefault();
dniEstado.innerHTML = `<div class="alert alert-warning mb-0">
<strong>Debe verificar el DNI.</strong>
Presione el botn Verificar antes de guardar el profesional.
</div>`;
dniEstado.style.display = '';
btnVerificar.focus();
}
});
})();
</script>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,298 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mi Perfil - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li> <li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li> </ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesión</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<div class="row justify-content-center">
<div class="col-12 col-lg-8">
<h1 class="h4 mb-3">Editar datos de administradora</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if(session('admin_action_error'))
<div class="alert alert-danger" role="alert">
{{ session('admin_action_error') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body">
<form id="formPerfilAdmin" method="POST" action="/administrador/perfil" class="row g-3">
@csrf
@method('PUT')
<input id="contra_actual_hidden" name="contra_actual" type="hidden">
<div class="col-12 col-md-6">
<label class="form-label" for="nombre">Nombre</label>
<input id="nombre" name="nombre" type="text" class="form-control" value="{{ old('nombre', $administrador->persona?->nombre ?? '') }}" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="apellido">Apellido</label>
<input id="apellido" name="apellido" type="text" class="form-control" value="{{ old('apellido', $administrador->persona?->apellido ?? '') }}" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="dni">DNI</label>
<input id="dni" type="text" class="form-control bg-light" value="Campo no editable" readonly>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="correo">Correo</label>
<input id="correo" name="correo" type="email" class="form-control" value="{{ old('correo', $administrador->correo) }}" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="telefono">Celular</label>
<input id="telefono" name="telefono" type="text" class="form-control" value="{{ old('telefono', $telefonoActual->telefono ?? '') }}" placeholder="Ej.: 3411234567">
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="usuario">Usuario</label>
<input id="usuario" name="usuario" type="text" class="form-control" value="{{ old('usuario', $administrador->credencial?->usuario ?? '') }}" required>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="contra">Nueva contraseña</label>
<input id="contra" name="contra" type="password" class="form-control" autocomplete="new-password" placeholder="Dejar en blanco para no cambiar">
<div class="form-text">Mínimo 6 caracteres.</div>
</div>
<div class="col-12 col-md-6">
<label class="form-label" for="contra_confirmation">Confirmar nueva contraseña</label>
<input id="contra_confirmation" name="contra_confirmation" type="password" class="form-control" autocomplete="new-password">
</div>
<div class="col-12 d-flex justify-content-end gap-2 mt-2">
<a href="/administrador/dashboard" class="btn btn-outline-secondary">Volver</a>
<button type="button" class="btn btn-outline-dark" data-bs-toggle="modal" data-bs-target="#modalPreguntaSecreta">
Configurar pregunta y respuesta secreta
</button>
<button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modalConfirmarCambiosAdmin">Guardar cambios</button>
</div>
</form>
</div>
</div>
<div class="modal fade" id="modalConfirmarCambiosAdmin" tabindex="-1" aria-labelledby="modalConfirmarCambiosAdminLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="modalConfirmarCambiosAdminLabel">Confirmar cambios</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<div class="modal-body">
<p class="text-muted small mb-3">Para guardar los cambios, ingresa tu contraseña actual.</p>
<div class="mb-0">
<label class="form-label" for="contra_actual_modal">Contraseña actual</label>
<input id="contra_actual_modal" type="password" class="form-control" autocomplete="current-password" required>
<div class="invalid-feedback">Debes ingresar tu contraseña actual para guardar los cambios.</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="button" id="btnConfirmarGuardarPerfilAdmin" class="btn btn-primary">Guardar cambios</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="modalPreguntaSecreta" tabindex="-1" aria-labelledby="modalPreguntaSecretaLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h2 class="modal-title fs-5" id="modalPreguntaSecretaLabel">Configurar pregunta y respuesta secreta</h2>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Cerrar"></button>
</div>
<form method="POST" action="/administrador/perfil/pregunta-secreta" class="row g-0">
@csrf
<div class="modal-body">
<div class="mb-3">
<label class="form-label" for="contra_actual_secreta">Contraseña actual</label>
<input id="contra_actual_secreta" name="contra_actual_secreta" type="password" class="form-control" autocomplete="current-password" required>
</div>
<div class="mb-3">
<label class="form-label" for="pregunta_secreta">Pregunta secreta</label>
<input id="pregunta_secreta" name="pregunta_secreta" type="text" class="form-control" value="{{ old('pregunta_secreta') }}" maxlength="255" required>
</div>
<div class="mb-0">
<label class="form-label" for="respuesta_secreta">Respuesta secreta</label>
<input id="respuesta_secreta" name="respuesta_secreta" type="text" class="form-control" maxlength="255" required>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Cancelar</button>
<button type="submit" class="btn btn-primary">Guardar</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
var formPerfil = document.getElementById('formPerfilAdmin');
var modalConfirmacion = document.getElementById('modalConfirmarCambiosAdmin');
var inputContraActualModal = document.getElementById('contra_actual_modal');
var inputContraActualHidden = document.getElementById('contra_actual_hidden');
var botonConfirmarGuardado = document.getElementById('btnConfirmarGuardarPerfilAdmin');
if (formPerfil && modalConfirmacion && inputContraActualModal && inputContraActualHidden && botonConfirmarGuardado) {
var enviarFormularioPerfil = function () {
if (inputContraActualModal.value === '') {
inputContraActualModal.classList.add('is-invalid');
inputContraActualModal.focus();
return;
}
inputContraActualModal.classList.remove('is-invalid');
inputContraActualHidden.value = inputContraActualModal.value;
if (typeof bootstrap !== 'undefined') {
bootstrap.Modal.getOrCreateInstance(modalConfirmacion).hide();
}
formPerfil.submit();
};
botonConfirmarGuardado.addEventListener('click', enviarFormularioPerfil);
inputContraActualModal.addEventListener('input', function () {
if (inputContraActualModal.value !== '') {
inputContraActualModal.classList.remove('is-invalid');
}
});
inputContraActualModal.addEventListener('keydown', function (event) {
if (event.key === 'Enter') {
event.preventDefault();
enviarFormularioPerfil();
}
});
modalConfirmacion.addEventListener('hidden.bs.modal', function () {
inputContraActualModal.value = '';
inputContraActualModal.classList.remove('is-invalid');
});
}
@if($errors->has('contra_actual'))
if (modalConfirmacion && typeof bootstrap !== 'undefined') {
bootstrap.Modal.getOrCreateInstance(modalConfirmacion).show();
}
@endif
@if($errors->has('contra_actual_secreta') || $errors->has('pregunta_secreta') || $errors->has('respuesta_secreta'))
var modalPreguntaSecreta = document.getElementById('modalPreguntaSecreta');
if (modalPreguntaSecreta && typeof bootstrap !== 'undefined') {
bootstrap.Modal.getOrCreateInstance(modalPreguntaSecreta).show();
}
@endif
});
</script>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,205 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Profesionales - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-3">Profesionales</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if(session('admin_action_error'))
<div class="alert alert-danger" role="alert">
{{ session('admin_action_error') }}
</div>
@endif
<div class="card border shadow-sm mb-3">
<div class="card-body">
<form method="GET" action="/administrador/profesionales" class="row g-3 align-items-end">
<div class="col-12 col-md-6">
<label for="q" class="form-label">Buscar profesional</label>
<input id="q" name="q" type="text" class="form-control" value="{{ request('q') }}" placeholder="Nombre, apellido, DNI, correo o matrícula">
</div>
<div class="col-12 col-md-3">
<label for="estado" class="form-label">Estado</label>
<select id="estado" name="estado" class="form-select">
<option value="">Todos</option>
<option value="activos" @selected(request('estado') === 'activos')>Activos</option>
<option value="inactivos" @selected(request('estado') === 'inactivos')>Inactivos</option>
</select>
</div>
<div class="col-12 col-md-3 d-flex gap-2">
<button type="submit" class="btn btn-primary w-100">Filtrar</button>
<a href="/administrador/profesionales" class="btn btn-outline-secondary w-100">Limpiar</a>
</div>
</form>
</div>
</div>
<div class="table-responsive bg-white border rounded shadow-sm">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Nombre</th>
<th>Apellido</th>
<th>DNI</th>
<th>Correo</th>
<th>Matrícula</th>
<th>Profesión</th>
<th>Rol</th>
<th>Baja</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
@forelse($profesionales as $profesional)
@php
$persona = $profesional->persona;
$rol = $profesional->credencialProfesional?->rol ?? '-';
$profesionActual = $profesional->profesion?->titulo ?? '-';
@endphp
<tr>
<td>{{ $profesional->id }}</td>
<td>{{ $persona?->nombre ?? '-' }}</td>
<td>{{ $persona?->apellido ?? '-' }}</td>
<td>{{ $profesional->dni }}</td>
<td>{{ $profesional->correo }}</td>
<td>{{ $profesional->matricula }}</td>
<td>{{ $profesionActual }}</td>
<td>{{ $rol }}</td>
<td>
@if((int) $profesional->baja_id !== 1)
<span class="badge text-bg-danger"></span>
@else
<span class="badge text-bg-success">No</span>
@endif
</td>
<td>
<div class="d-flex flex-wrap gap-2">
<form method="POST" action="/administrador/profesionales/{{ $profesional->id }}/baja" onsubmit="return confirm('{{ (int) $profesional->baja_id !== 1 ? '¿Confirmás reactivar a este profesional?' : '¿Confirmás dar de baja a este profesional?' }}');">
@csrf
<button type="submit" class="btn btn-sm {{ (int) $profesional->baja_id !== 1 ? 'btn-outline-success' : 'btn-outline-danger' }}" title="{{ (int) $profesional->baja_id !== 1 ? 'Reactivar profesional' : 'Dar de baja' }}" aria-label="{{ (int) $profesional->baja_id !== 1 ? 'Reactivar profesional' : 'Dar de baja' }}"></button>
</form>
<a href="/administrador/profesionales/{{ $profesional->id }}/editar" class="btn btn-sm btn-outline-primary" title="Editar" aria-label="Editar">✏️</a>
<a href="/administrador/profesionales/{{ $profesional->id }}/asignaciones" class="btn btn-sm btn-outline-secondary" title="Asignar servicios" aria-label="Asignar servicios">⚙️</a>
</div>
</td>
</tr>
@empty
<tr>
<td colspan="10" class="text-center text-muted py-4">No hay profesionales cargados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
@if($profesionales->hasPages())
<div class="d-flex justify-content-center mt-3">
{{ $profesionales->links() }}
</div>
@endif
<div class="mt-3 d-flex justify-content-center">
<a href="/administrador/profesionales/nuevo" class="btn btn-primary">Agregar nuevo profesional</a>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,171 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Bugs - Administrador</title>
<script>
(function () {
const root = document.documentElement;
root.classList.add('sidebar-nav-pending');
const releasePending = function () {
root.classList.remove('sidebar-nav-pending');
};
const timeoutId = window.setTimeout(releasePending, 1200);
if (document.querySelector('.admin-sidebar')) {
window.clearTimeout(timeoutId);
releasePending();
return;
}
const observer = new MutationObserver(function () {
if (!document.querySelector('.admin-sidebar')) {
return;
}
observer.disconnect();
window.clearTimeout(timeoutId);
releasePending();
});
observer.observe(document.documentElement, {
childList: true,
subtree: true,
});
})();
</script>
<style>
@media (min-width: 992px) {
html.sidebar-nav-pending main {
visibility: hidden;
}
}
</style>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/administrador/dashboard">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
<span class="app-navbar-greeting ms-2">¡Hola, Administrador!</span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/profesionales">Profesionales</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/dashboard">Mis Datos</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/contenido-web">Contenido</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/emails">Emails</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/logs">Logs</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/fallas">Fallas</a></li>
<li class="nav-item">
<a class="btn app-navbar-link position-relative" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
</li>
<li class="nav-item"><a class="btn app-navbar-link" href="/administrador/backups">Backup</a></li>
</ul>
<a class="btn app-navbar-link" href="/logout">Cerrar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-4 flex-grow-1">
<h1 class="h4 mb-4">Reportes de bugs</h1>
@if(session('admin_action_success'))
<div class="alert alert-success" role="alert">
{{ session('admin_action_success') }}
</div>
@endif
@if(session('admin_action_info'))
<div class="alert alert-info" role="alert">
{{ session('admin_action_info') }}
</div>
@endif
<div class="card border shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-striped table-hover mb-0 align-middle">
<thead class="table-light">
<tr>
<th>ID</th>
<th>Título</th>
<th>Descripción</th>
<th>Estado</th>
<th>Foto</th>
<th>Fecha</th>
<th>Acciones</th>
</tr>
</thead>
<tbody>
@forelse($bugs as $bug)
<tr>
<td>{{ $bug->id }}</td>
<td>{{ $bug->titulo }}</td>
<td style="min-width: 280px; white-space: pre-line;">{{ $bug->descripcion }}</td>
<td>
@if(mb_strtolower(trim((string) $bug->estado)) === 'pendiente')
<span class="badge text-bg-warning">Pendiente</span>
@elseif(mb_strtolower(trim((string) $bug->estado)) === 'visto')
<span class="badge text-bg-success">Visto</span>
@else
<span class="badge text-bg-secondary">{{ $bug->estado }}</span>
@endif
</td>
<td>
@if($bug->fotoBug?->nombre)
<a href="{{ asset('images/bugs/' . $bug->fotoBug->nombre) }}" target="_blank" class="btn btn-sm btn-outline-primary">Ver foto</a>
@else
<span class="text-muted">Sin foto</span>
@endif
</td>
<td>{{ optional($bug->created_at)->format('d/m/Y H:i') }}</td>
<td>
@if(mb_strtolower(trim((string) $bug->estado)) === 'pendiente')
<form method="POST" action="/administrador/bugs/{{ $bug->id }}/marcar-visto" onsubmit="return confirm('¿Marcar este reporte como visto?');">
@csrf
<button type="submit" class="btn btn-sm btn-outline-success">Marcar como visto</button>
</form>
@else
<span class="text-muted">-</span>
@endif
</td>
</tr>
@empty
<tr>
<td colspan="7" class="text-center text-muted py-4">No hay bugs reportados.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
+32 -6
View File
@@ -12,8 +12,11 @@
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1">Login de Cliente</h1>
<p class="text-muted mb-4">Ingresa con tu correo y contraseña</p>
<h1 class="h4 mb-1 text-center">Login de Cliente</h1>
<div class="alert alert-info" role="alert">
Para iniciar sesión, primero debes ser cliente del estudio jurídico.
</div>
@if (session('login_error'))
<div class="alert alert-danger" role="alert">
@@ -27,6 +30,20 @@
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="alert alert-warning py-2" role="alert">
<strong>Intentos restantes:</strong> {{ $intentosRestantes ?? 5 }} de {{ $intentosMaximos ?? 5 }}
</div>
<form method="POST" action="/login/cliente" class="d-grid gap-3">
@csrf
<div>
@@ -39,19 +56,28 @@
<input class="form-control" id="contra" name="contra" type="password" required>
</div>
<div style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;" aria-hidden="true">
<label class="form-label" for="website">No completar este campo</label>
<input class="form-control" id="website" name="website" type="text" value="{{ old('website') }}" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit">Ingresar</button>
</form>
<div class="text-center mt-3">
<a href="/cliente/recuperar-credenciales" class="small text-muted">¿Olvidó sus credenciales?</a>
</div>
<hr class="my-4">
<p class="mb-0 text-center">
¿Sos profesional o administrador?
<a href="/login/personal">Ir a login de personal</a>
</p>
<div class="d-grid">
<a href="/" class="btn btn-outline-secondary">Volver al inicio</a>
</div>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
+33 -14
View File
@@ -12,8 +12,7 @@
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1">Login de Personal</h1>
<p class="text-muted mb-4">Acceso para profesionales y administradores.</p>
<h1 class="h4 mb-1 text-center">Login de Personal</h1>
@if (session('login_error'))
<div class="alert alert-danger" role="alert">
@@ -27,6 +26,20 @@
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="alert alert-warning py-2" role="alert">
<strong>Intentos restantes:</strong> {{ $intentosRestantes ?? 5 }} de {{ $intentosMaximos ?? 5 }}
</div>
<form method="POST" action="/login/personal" class="d-grid gap-3">
@csrf
<div>
@@ -39,24 +52,30 @@
<input class="form-control" id="contra" name="contra" type="password" required>
</div>
<div style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;" aria-hidden="true">
<label class="form-label" for="website">No completar este campo</label>
<input class="form-control" id="website" name="website" type="text" value="{{ old('website') }}" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit">Ingresar</button>
</form>
<hr class="my-4">
<p class="mb-0 text-center">
¿Sos cliente?
<a href="/login/cliente">Ir a login de cliente</a>
</p>
</div>
</div>
</div>
</div>
<div class="text-center mt-4">
<a href="/" class="btn btn-secondary">Agregar Administrador</a>
<div class="text-center mt-3">
<a href="/personal/recuperar-credenciales" class="small text-muted">Recuperacion para personal</a>
</div>
<div class="text-center mt-2">
<a href="/" class="btn btn-secondary">Agregar Profesional</a>
<a href="/admin/recuperar-credenciales" class="small text-muted">Recuperación para administrador</a>
</div>
<hr class="my-4">
<div class="d-grid mt-3">
<a href="/" class="btn btn-outline-secondary">Volver al inicio</a>
</div>
</div>
</div>
</div>
</div>
</main>
@include('partials.reportar-falla-boton')
</body>
</html>
@@ -0,0 +1,55 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nuevas credenciales administrador</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Nuevas credenciales administrador</h1>
<p class="text-muted text-center small mb-4">Ingresá tu nuevo usuario y tu nueva contraseña de administrador.</p>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="/admin/recuperar-credenciales/{{ $token }}" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="usuario">Nuevo usuario</label>
<input class="form-control" id="usuario" name="usuario" type="text" value="{{ old('usuario', $usuarioActual ?? '') }}" required autofocus autocomplete="username">
</div>
<div>
<label class="form-label" for="contra">Nueva contraseña</label>
<input class="form-control" id="contra" name="contra" type="password" required minlength="6" maxlength="30" autocomplete="new-password">
</div>
<div>
<label class="form-label" for="contra_confirmation">Confirmar contraseña</label>
<input class="form-control" id="contra_confirmation" name="contra_confirmation" type="password" required minlength="6" maxlength="30" autocomplete="new-password">
</div>
<button class="btn btn-primary" type="submit">Guardar credenciales</button>
</form>
<hr class="my-4">
<div class="d-grid">
<a href="/login/personal" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,51 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nueva contraseña personal</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Nueva contraseña</h1>
<p class="text-muted text-center small mb-4">Ingresá tu nueva contraseña para el acceso de personal.</p>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="/personal/recuperar-credenciales/{{ $token }}" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="contra">Nueva contraseña</label>
<input class="form-control" id="contra" name="contra" type="password" required autofocus minlength="6" maxlength="30">
</div>
<div>
<label class="form-label" for="contra_confirmation">Confirmar contraseña</label>
<input class="form-control" id="contra_confirmation" name="contra_confirmation" type="password" required minlength="6" maxlength="30">
</div>
<button class="btn btn-primary" type="submit">Guardar contraseña</button>
</form>
<hr class="my-4">
<div class="d-grid">
<a href="/login/personal" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,51 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Nueva contraseña</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Nueva contraseña</h1>
<p class="text-muted text-center small mb-4">Ingresá tu nueva contraseña y confirmala para continuar.</p>
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="/cliente/recuperar-credenciales/{{ $token }}" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="contra">Nueva contraseña</label>
<input class="form-control" id="contra" name="contra" type="password" required autofocus minlength="6" maxlength="30">
</div>
<div>
<label class="form-label" for="contra_confirmation">Confirmar contraseña</label>
<input class="form-control" id="contra_confirmation" name="contra_confirmation" type="password" required minlength="6" maxlength="30">
</div>
<button class="btn btn-primary" type="submit">Guardar contraseña</button>
</form>
<hr class="my-4">
<div class="d-grid">
<a href="/login/cliente" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,142 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recuperar credenciales administrador</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Recuperar credenciales administrador</h1>
<p class="text-muted text-center small mb-4">Completá todos los datos con los que está registrado en el sistema</p>
@if(session('recuperar_success'))
<div class="alert alert-success" role="alert">
{{ session('recuperar_success') }}
</div>
@endif
@if(session('recuperar_error'))
<div class="alert alert-danger" role="alert">
{{ session('recuperar_error') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="alert alert-warning py-2" role="alert">
<strong>Intentos restantes:</strong> {{ $intentosRestantes ?? 5 }} de {{ $intentosMaximos ?? 5 }}
</div>
<form method="POST" action="/admin/recuperar-credenciales" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="celular">Número de celular</label>
<input class="form-control" id="celular" name="celular" type="text" value="{{ old('celular') }}" required autofocus>
</div>
<div>
<label class="form-label" for="nombre">Nombre</label>
<input class="form-control" id="nombre" name="nombre" type="text" value="{{ old('nombre') }}" required>
</div>
<div>
<label class="form-label" for="apellido">Apellido</label>
<input class="form-control" id="apellido" name="apellido" type="text" value="{{ old('apellido') }}" required>
</div>
<div>
<label class="form-label" for="correo">Correo electrónico</label>
<input class="form-control" id="correo" name="correo" type="email" value="{{ old('correo') }}" required>
</div>
<div>
<label class="form-label" for="pregunta_secreta">Pregunta secreta</label>
<input class="form-control" id="pregunta_secreta" name="pregunta_secreta" type="text" value="{{ old('pregunta_secreta') }}" placeholder="Ingresá el correo para visualizar la pregunta" readonly>
<div id="pregunta-secreta-ayuda" class="form-text">La pregunta se mostrará automáticamente según el correo ingresado.</div>
</div>
<div>
<label class="form-label" for="respuesta_secreta">Respuesta secreta</label>
<input class="form-control" id="respuesta_secreta" name="respuesta_secreta" type="text" value="{{ old('respuesta_secreta') }}" required>
</div>
<div style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;" aria-hidden="true">
<label class="form-label" for="website">No completar este campo</label>
<input class="form-control" id="website" name="website" type="text" value="{{ old('website') }}" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit">Enviar enlace</button>
</form>
<hr class="my-4">
<div class="d-grid">
<a href="/login/personal" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
<script>
document.addEventListener('DOMContentLoaded', function () {
const correoInput = document.getElementById('correo');
const preguntaInput = document.getElementById('pregunta_secreta');
const ayuda = document.getElementById('pregunta-secreta-ayuda');
if (!correoInput || !preguntaInput || !ayuda) {
return;
}
const limpiarPregunta = (mensaje = 'La pregunta se mostrará automáticamente según el correo ingresado.') => {
preguntaInput.value = '';
ayuda.textContent = mensaje;
};
const cargarPregunta = async () => {
const correo = correoInput.value.trim();
if (correo === '') {
limpiarPregunta();
return;
}
ayuda.textContent = 'Buscando pregunta secreta...';
try {
const response = await fetch('/admin/recuperar-credenciales/pregunta?correo=' + encodeURIComponent(correo), {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const data = await response.json();
if (data.encontrada && data.pregunta) {
preguntaInput.value = data.pregunta;
ayuda.textContent = 'Respondé la pregunta secreta para continuar.';
} else {
limpiarPregunta('No se encontró una pregunta secreta visible para ese correo.');
}
} catch (error) {
limpiarPregunta('No se pudo cargar la pregunta secreta. Intentá nuevamente.');
}
};
correoInput.addEventListener('blur', cargarPregunta);
correoInput.addEventListener('change', cargarPregunta);
if (correoInput.value.trim() !== '') {
cargarPregunta();
}
});
</script>
</body>
</html>
@@ -0,0 +1,84 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recuperar credenciales personal</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Recuperar credenciales</h1>
<p class="text-muted text-center small mb-4">Ingresá los datos con los que estás registrado en el sistema para recibir el enlace de recuperación.</p>
@if(session('recuperar_success'))
<div class="alert alert-success" role="alert">
{{ session('recuperar_success') }}
</div>
@endif
@if(session('recuperar_error'))
<div class="alert alert-danger" role="alert">
{{ session('recuperar_error') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="alert alert-warning py-2" role="alert">
<strong>Intentos restantes:</strong> {{ $intentosRestantes ?? 5 }} de {{ $intentosMaximos ?? 5 }}
</div>
<form method="POST" action="/personal/recuperar-credenciales" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="dni">DNI</label>
<input class="form-control" id="dni" name="dni" type="text" value="{{ old('dni') }}" required autofocus>
</div>
<div>
<label class="form-label" for="matricula">Matrícula</label>
<input class="form-control" id="matricula" name="matricula" type="text" value="{{ old('matricula') }}" required>
</div>
<div>
<label class="form-label" for="profesion_id">Profesión</label>
<select class="form-select" id="profesion_id" name="profesion_id" required>
<option value="">Seleccionar profesión</option>
@foreach(($profesiones ?? []) as $profesion)
<option value="{{ $profesion->id }}" @selected((string) old('profesion_id') === (string) $profesion->id)>{{ $profesion->titulo }}</option>
@endforeach
</select>
</div>
<div>
<label class="form-label" for="correo">Correo electrónico</label>
<input class="form-control" id="correo" name="correo" type="email" value="{{ old('correo') }}" required>
</div>
<div style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;" aria-hidden="true">
<label class="form-label" for="website">No completar este campo</label>
<input class="form-control" id="website" name="website" type="text" value="{{ old('website') }}" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit">Enviar enlace</button>
</form>
<hr class="my-4">
<div class="d-grid">
<a href="/login/personal" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
@@ -0,0 +1,71 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Recuperar credenciales</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5">
<div class="row justify-content-center">
<div class="col-12 col-sm-10 col-md-8 col-lg-5">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h4 mb-1 text-center">Recuperar credenciales</h1>
<p class="text-muted text-center small mb-4">Ingresá tu correo electrónico y te enviaremos un enlace para restablecer tu contraseña.</p>
@if(session('recuperar_success'))
<div class="alert alert-success" role="alert">
{{ session('recuperar_success') }}
</div>
@endif
@if(session('recuperar_error'))
<div class="alert alert-danger" role="alert">
{{ session('recuperar_error') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<div class="alert alert-warning py-2" role="alert">
<strong>Intentos restantes:</strong> {{ $intentosRestantes ?? 5 }} de {{ $intentosMaximos ?? 5 }}
</div>
<form method="POST" action="/cliente/recuperar-credenciales" class="d-grid gap-3">
@csrf
<div>
<label class="form-label" for="correo">Correo electrónico con el que está registrado</label>
<input class="form-control" id="correo" name="correo" type="email" value="{{ old('correo') }}" required autofocus>
</div>
<div style="position:absolute; left:-9999px; width:1px; height:1px; overflow:hidden;" aria-hidden="true">
<label class="form-label" for="website">No completar este campo</label>
<input class="form-control" id="website" name="website" type="text" value="{{ old('website') }}" tabindex="-1" autocomplete="off">
</div>
<button class="btn btn-primary" type="submit">Enviar enlace</button>
</form>
<div class="alert alert-info mt-3 mb-0" role="alert">
Si no recibis el correo en los proximos 5 minutos o tenes otro problema, comunicate algun profesional de Abogadas del Litoral.
</div>
<hr class="my-4">
<div class="d-grid">
<a href="/login/cliente" class="btn btn-outline-secondary">Volver al login</a>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
</html>
+121
View File
@@ -0,0 +1,121 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Instrucciones - Abogadas del Litoral</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body id="top" class="d-flex flex-column min-vh-100 bg-light">
<header class="app-navbar">
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/">
<div class="d-flex align-items-center justify-content-center" style="width: 130px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="btn app-navbar-link" href="/">Inicio</a></li>
<li class="nav-item"><a class="btn app-navbar-link" href="/instrucciones-uso">¿Necesitas Ayuda?</a></li>
</ul>
<a class="btn app-navbar-link" href="/login/cliente">Iniciar Sesion</a>
</div>
</div>
</nav>
</header>
<main class="container py-5 flex-grow-1">
<section class="mx-auto" style="max-width: 980px;">
<h1 class="h3 mb-4 text-center">Instrucciones para utilizar el sitio web</h1>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4 p-md-5">
<h2 class="h5 mb-3">Navegación del sitio</h2>
<p class="text-muted mb-3">
Para navegar a través del sitio web, podrá utilizar los botones que aparecen en la parte superior de la misma, o también podrá utilizar la rueda de su mouse para visualizar los distintos apartados de la página. Si quiere volver al inicio de la página, podrá apretar un botón redondo azul con una flecha apuntando hacia arriba en la parte inferior derecha.
</p>
<p class="text-muted mb-0">
Para visualizar todos los servicios y profesionales, usted podrá presionar los botones laterales para ir viendo todos los servicios y profesionales que el estudio jurídico ofrece.
</p>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4 p-md-5">
<h2 class="h5 mb-3">¿Qué hacer en caso de detectar un error en la página?</h2>
<p class="text-muted mb-0">
Si se encuentra con un error en el sitio, agradeceríamos que lo reporte con el botón que aparece en la parte superior de la página con el símbolo 🐞. Al apretarlo se sacará una foto a su pantalla automáticamente y se le pedirá que complete un pequeño formulario explicando qué fue lo que ocurrió.
</p>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4 p-md-5">
<h2 class="h5 mb-3">¿Por qué no puedo crear un usuario para iniciar sesión?</h2>
<p class="text-muted mb-0">
Los usuarios son creados por los profesionales, es decir, usted primero debe tener una reunión con un profesional y él le creará un usuario y contraseña para poder ingresar al sitio.
</p>
</div>
</div>
<div class="card shadow-sm border-0 mb-4">
<div class="card-body p-4 p-md-5">
<h2 class="h5 mb-3">¿Cómo puedo utilizar a Clara, la asistente virtual?</h2>
<p class="text-muted mb-3">
Clara es un asistente virtual que da respuestas dependiendo de la palabra clave que el usuario utilice. Por ejemplo, si el usuario quiere preguntar por los honorarios de los profesionales, debería utilizar alguna de las siguientes palabras claves: honorario, honorarios, precio, precios, costo, costos, etc.
</p>
<p class="text-muted mb-0">
Si utiliza una palabra que la asistente no conoce, lamentablemente no podrá responder a su duda. En estos casos se enviará un reporte automático al administrador informándole que el asistente no pudo responder una pregunta para poder corregir el problema.
</p>
</div>
</div>
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h2 class="h5 mb-3">¿Cómo si luego de pedir un turno se me asignó un turno?</h2>
<p class="text-muted mb-0">
En caso de que algún profesional acepte su caso, usted deberá revisar regularmente su correo electrónico con el que envió el formulario, a través de este medio se le informará todos los detalles de su turno.
</p>
</div>
</div>
</section>
</main>
<footer class="app-footer mt-auto py-4">
<div class="container">
<div class="row g-4 align-items-start text-center">
<div class="col-12 col-md-4">
<div class="d-inline-flex align-items-center justify-content-center" style="width: 120px; height: 52px;">
<img src="{{ asset('images/logo.png') }}" alt="Logo" class="img-fluid" style="max-height: 70px; width: auto; object-fit: contain;">
</div>
</div>
<div class="col-12 col-md-4 text-center">
<h3 class="h6 mb-2">Redes Sociales</h3>
<p class="mb-2"><a href="https://www.instagram.com/abogadasdellitoral?utm_source=ig_web_button_share_sheet&igsh=ZDNlZDc0MzIxNw==" target="_blank" rel="noopener noreferrer">Instagram</a></p>
<p class="small mb-0">Desarrollado por Luciano Belini</p>
</div>
<div class="col-12 col-md-4">
<h3 class="h6 mb-2">Ubicacion</h3>
<p class="mb-0"><a href="https://www.google.com/maps/place/Dr.+Luis+Pasteur+141,+Paran%C3%A1,+Entre+R%C3%ADos,+Argentina" target="_blank" rel="noopener noreferrer">Dr. Luis Pasteur 141, Paraná, Entre Ríos, Argentina</a></p>
</div>
</div>
</div>
</footer>
<a href="#top" class="btn btn-primary rounded-circle position-fixed bottom-0 end-0 m-4 d-flex align-items-center justify-content-center" style="width: 44px; height: 44px;" aria-label="Volver arriba" title="Volver arriba">
</a>
@include('partials.reportar-falla-boton')
</body>
</html>
-85
View File
@@ -1,85 +0,0 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@yield('title', 'Abogadas Litoral')</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style>
:root {
--site-navbar-bg: #f4ece5;
--site-navbar-text: #2f2721;
--site-navbar-hover: #1f1915;
}
.site-navbar {
background-color: var(--site-navbar-bg);
}
.site-navbar .navbar-brand,
.site-navbar .nav-link,
.site-navbar .btn-link {
color: var(--site-navbar-text);
}
.site-navbar .nav-link:hover,
.site-navbar .btn-link:hover {
color: var(--site-navbar-hover);
}
</style>
</head>
<body class="d-flex flex-column min-vh-100 bg-light">
<header class="border-bottom">
<nav class="navbar navbar-expand-lg site-navbar">
<div class="container">
<a class="navbar-brand d-flex align-items-center" href="/">
<div class="border rounded d-flex align-items-center justify-content-center bg-white" style="width: 120px; height: 48px;">
<span class="small text-muted">LOGO</span>
</div>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#menuPrincipal" aria-controls="menuPrincipal" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menuPrincipal">
<ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"><a class="nav-link" href="#servicios">Servicios</a></li>
<li class="nav-item"><a class="nav-link" href="#quienes-somos">Quienes Somos</a></li>
<li class="nav-item"><a class="nav-link" href="#equipo">Equipo</a></li>
<li class="nav-item"><a class="nav-link" href="#ubicacion">Ubicacion</a></li>
<li class="nav-item"><a class="nav-link" href="#formulario">Formulario</a></li>
</ul>
<a class="btn btn-primary" href="/login/personal">Iniciar Sesion</a>
</div>
</div>
</nav>
</header>
@yield('content')
<footer class="bg-white border-top mt-auto py-4">
<div class="container">
<div class="row g-4 align-items-start text-center">
<div class="col-12 col-md-4">
<div class="border rounded d-inline-flex align-items-center justify-content-center" style="width: 120px; height: 48px;">
<span class="small text-muted">LOGO</span>
</div>
</div>
<div class="col-12 col-md-4 text-center">
<h3 class="h6 mb-2">Redes Sociales</h3>
<p class="mb-2">Instagram | Facebook | LinkedIn</p>
<p class="small text-muted mb-0">Desarrollado por Luciano Belini</p>
</div>
<div class="col-12 col-md-4">
<h3 class="h6 mb-2">Ubicacion</h3>
<p class="mb-0">Direccion pendiente de definir</p>
</div>
</div>
</div>
</footer>
</body>
</html>
@@ -0,0 +1,19 @@
<aside class="admin-sidebar" aria-label="Menu administrador">
<nav class="admin-sidebar-nav">
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/profesionales*') ? 'active' : '' }}" href="/administrador/profesionales">Profesionales</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/dashboard') ? 'active' : '' }}" href="/administrador/dashboard">Mis Datos</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/contenido-web*') ? 'active' : '' }}" href="/administrador/contenido-web">Contenido</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/emails*') ? 'active' : '' }}" href="/administrador/emails">Emails</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/logs*') ? 'active' : '' }}" href="/administrador/logs">Logs</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/fallas*') ? 'active' : '' }}" href="/administrador/fallas">Fallas</a>
<a class="btn app-navbar-link admin-sidebar-link position-relative {{ request()->is('administrador/bugs*') ? 'active' : '' }}" href="/administrador/bugs">
Bugs
@if(($bugsPendientesCount ?? 0) > 0)
<span class="position-absolute top-0 start-100 translate-middle p-1 bg-danger rounded-circle">
<span class="visually-hidden">Hay bugs pendientes</span>
</span>
@endif
</a>
<a class="btn app-navbar-link admin-sidebar-link {{ request()->is('administrador/backups*') ? 'active' : '' }}" href="/administrador/backups">Backup</a>
</nav>
</aside>
@@ -0,0 +1,65 @@
<a
id="reportar-falla-boton"
href="/reportar-falla?origen={{ urlencode(request()->fullUrl()) }}"
class="btn btn-sm text-danger border-0 d-none reportar-falla-btn"
title="Reportar un error"
>
🐞
</a>
<style>
.reportar-falla-btn {
transition: background-color 0.18s ease, color 0.18s ease;
}
.reportar-falla-btn:hover,
.reportar-falla-btn:focus-visible {
background-color: #dc3545;
color: #fff !important;
}
</style>
<script>
(function () {
const boton = document.getElementById('reportar-falla-boton');
if (!boton) {
return;
}
const navbarBrand = document.querySelector('.app-navbar .navbar-brand, .navbar-brand');
if (navbarBrand && navbarBrand.parentElement) {
boton.classList.remove('d-none');
boton.classList.add('ms-2', 'align-self-center');
navbarBrand.insertAdjacentElement('afterend', boton);
} else {
// Fallback para pantallas sin navbar compatible.
boton.classList.remove('d-none');
boton.classList.add('position-fixed', 'end-0', 'bottom-0', 'm-3', 'shadow-sm');
boton.style.zIndex = '1040';
}
boton.addEventListener('click', function (e) {
e.preventDefault();
const destino = boton.getAttribute('href');
const capturar = window.html2canvas
? window.html2canvas(document.body, { useCORS: true, scale: 0.6, logging: false })
: Promise.reject();
boton.textContent = '⏳';
boton.style.pointerEvents = 'none';
capturar
.then(function (canvas) {
try {
sessionStorage.setItem('bug_captura', canvas.toDataURL('image/jpeg', 0.75));
} catch (_) { /* sessionStorage lleno o bloqueado */ }
window.location.href = destino;
})
.catch(function () {
window.location.href = destino;
});
});
})();
</script>
+111
View File
@@ -0,0 +1,111 @@
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Reportar falla</title>
@vite(['resources/css/app.css', 'resources/js/app.js'])
</head>
<body class="bg-light">
<main class="container py-5" style="max-width: 760px;">
<div class="card shadow-sm border-0">
<div class="card-body p-4 p-md-5">
<h1 class="h3 mb-3">Reportar falla</h1>
<p class="text-muted mb-4">Describí brevemente el problema que encontraste. El reporte se guarda para su revisión.</p>
@if(session('bug_success'))
<div class="alert alert-success" role="alert">
{{ session('bug_success') }}
</div>
@endif
@if($errors->any())
<div class="alert alert-danger" role="alert">
<ul class="mb-0 ps-3">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form action="/reportar-falla" method="POST" enctype="multipart/form-data" class="row g-3">
@csrf
<input type="hidden" name="origen" value="{{ old('origen', $origen ?? '') }}">
<input type="hidden" name="captura_pantalla" id="captura_pantalla_input">
<div class="col-12">
<label for="titulo" class="form-label">Título</label>
<input type="text" id="titulo" name="titulo" class="form-control" value="{{ old('titulo') }}" maxlength="255" required>
</div>
<div class="col-12">
<label for="descripcion" class="form-label">Descripción</label>
<textarea id="descripcion" name="descripcion" class="form-control" rows="6" maxlength="4500" placeholder="Describa paso por paso lo que ocurrió, nos ayudará a resolver el problema." required>{{ old('descripcion') }}</textarea>
</div>
<div class="col-12" id="captura-preview-container" style="display:none;">
<label class="form-label">Captura de pantalla adjunta automáticamente</label>
<div class="border rounded p-2 bg-white d-flex align-items-start gap-3">
<img id="captura-preview" src="" alt="Captura de pantalla" class="img-fluid rounded" style="max-height: 180px; object-fit: contain; border: 1px solid #dee2e6;">
<button type="button" id="captura-quitar" class="btn btn-sm btn-outline-secondary">Quitar captura</button>
</div>
</div>
<div class="col-12">
<label for="foto" class="form-label">Foto adicional <span class="text-muted">(opcional)</span></label>
<input type="file" id="foto" name="foto" class="form-control @error('foto') is-invalid @enderror" accept="image/jpeg,image/png,image/webp">
@error('foto')
<div class="invalid-feedback">{{ $message }}</div>
@enderror
<div class="form-text">Podés adjuntar una captura o foto adicional del problema.</div>
</div>
@if(($origen ?? '') !== '')
<div class="col-12">
<label class="form-label">Página desde donde se reportó</label>
<input type="text" class="form-control" value="{{ $origen }}" readonly>
</div>
@endif
<div class="col-12 d-flex justify-content-between gap-2 flex-wrap">
<a href="{{ ($origen ?? '') !== '' ? $origen : '/' }}" class="btn btn-outline-secondary">Volver</a>
<button type="submit" class="btn btn-danger">Enviar reporte</button>
</div>
</form>
</div>
</div>
</main>
<script>
(function () {
const captura = sessionStorage.getItem('bug_captura');
if (!captura) return;
const input = document.getElementById('captura_pantalla_input');
const preview = document.getElementById('captura-preview');
const container = document.getElementById('captura-preview-container');
const quitar = document.getElementById('captura-quitar');
if (input) input.value = captura;
if (preview) preview.src = captura;
if (container) container.style.display = '';
if (quitar) {
quitar.addEventListener('click', function () {
sessionStorage.removeItem('bug_captura');
if (input) input.value = '';
if (container) container.style.display = 'none';
});
}
// Limpiar sessionStorage al enviar el formulario.
const form = document.querySelector('form[action="/reportar-falla"]');
if (form) {
form.addEventListener('submit', function () {
sessionStorage.removeItem('bug_captura');
});
}
})();
</script>
</body>
</html>