Archivos de configuración global, dependencias y assets base

This commit is contained in:
Lucho
2026-06-24 16:30:14 -03:00
parent 9474236226
commit 4d29f6bb49
11 changed files with 2046 additions and 51 deletions
+226
View File
@@ -1 +1,227 @@
@import 'bootstrap/dist/css/bootstrap.min.css';
html {
font-size: 18px;
}
.app-navbar {
background: #ffb555;
border-bottom: 1px solid #ffb13c;
}
.app-navbar .navbar-toggler {
border-color: rgba(255, 255, 255, 0.45);
}
.app-navbar .navbar-toggler:focus {
box-shadow: 0 0 0 0.2rem rgba(255, 255, 255, 0.2);
}
.app-navbar .navbar-toggler-icon {
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.92%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
}
.app-navbar .navbar-nav {
gap: 0.5rem;
}
.app-navbar-link {
color: #4d4545;
background: transparent;
border: 2px solid transparent;
font-weight: 600;
}
.app-navbar-link:hover,
.app-navbar-link:focus,
.app-navbar-link:active {
color: #352e2e;
background: rgba(255, 255, 255, 0.16);
border-color: rgba(255, 255, 255, 0.24);
}
.app-navbar-link.active,
.app-navbar-link[aria-current='page'] {
color: #fff;
background: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.3);
}
.app-navbar .navbar-brand,
.app-navbar .navbar-brand:hover,
.app-navbar .navbar-brand:focus {
color: #fff;
font-weight: 700;
}
.app-navbar-greeting {
color: #fff;
font-size: 0.95rem;
font-weight: 700;
white-space: nowrap;
}
@media (max-width: 991.98px) {
.app-navbar-greeting {
display: none;
}
}
.app-footer {
background: #ff9720;
border-top: 1px solid #ec7f1a;
color: #fff;
}
.app-footer h3,
.app-footer p,
.app-footer .small {
color: inherit;
}
.app-footer a {
color: #fff;
text-decoration: none;
border-bottom: 1px solid rgba(255, 255, 255, 0.35);
}
.app-footer a:hover,
.app-footer a:focus {
color: #fff;
border-bottom-color: rgba(255, 255, 255, 0.85);
}
.servicio-card-img {
width: 100%;
height: 200px;
object-fit: cover;
object-position: center;
}
html.sidebar-nav-pending .app-navbar #menuPrincipal > ul,
html.sidebar-nav-pending .app-navbar .navbar-toggler {
visibility: hidden;
pointer-events: none;
}
/* Evita el flash del menu horizontal en pantallas que luego se convierten a sidebar. */
.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/']),
.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/']) {
visibility: hidden;
pointer-events: none;
}
body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/'])) .app-navbar .navbar-toggler,
body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/'])) .app-navbar .navbar-toggler {
visibility: hidden;
pointer-events: none;
}
/* Reserva el ancho del sidebar antes de que JS agregue clases y evita el "flash" de layout full-width. */
@media (min-width: 992px) {
body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/administrador/'])) main,
body:has(.app-navbar #menuPrincipal > ul:has(a[href^='/profesional/'])) main {
margin-left: 250px;
width: calc(100% - 250px);
}
html.sidebar-nav-pending main.sidebar-main-pending {
visibility: hidden;
}
}
.admin-sidebar {
position: fixed;
top: var(--admin-navbar-height, 74px);
left: 0;
width: 250px;
height: calc(100vh - var(--admin-navbar-height, 74px));
overflow-y: auto;
background: #ffffff;
border-right: 1px solid #f0d39c;
padding: 1rem 0.75rem;
z-index: 1030;
}
.admin-sidebar-nav {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.admin-sidebar-link {
text-align: left;
width: 100%;
}
.admin-sidebar-details {
width: 100%;
}
.admin-sidebar-details > summary {
list-style: none;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.admin-sidebar-details > summary::-webkit-details-marker {
display: none;
}
.admin-sidebar-details > summary::after {
content: '\203A';
font-size: 1.1rem;
line-height: 1;
transition: transform 0.2s ease;
display: inline-block;
}
.admin-sidebar-details[open] > summary::after {
transform: rotate(90deg);
}
.admin-sidebar-sublink {
padding-left: 1.5rem !important;
font-size: 0.9em;
width: 100%;
text-align: left;
}
.admin-sidebar .admin-sidebar-link:hover,
.admin-sidebar .admin-sidebar-link:focus,
.admin-sidebar .admin-sidebar-link:active {
color: #352e2e;
background: #f6f7f9;
border-color: #e6e9ef;
}
.admin-sidebar .admin-sidebar-link.active,
.admin-sidebar .admin-sidebar-link[aria-current='page'] {
color: #352e2e;
background: #ffe0b5;
border-color: #ffb555;
}
.admin-main-content {
margin-left: 250px;
width: calc(100% - 250px);
}
@media (max-width: 991.98px) {
.admin-sidebar {
position: static;
top: auto;
left: auto;
width: 100%;
height: auto;
border-right: 0;
border-bottom: 1px solid #f0d39c;
}
.admin-main-content {
margin-left: 0;
width: 100%;
}
}
+542 -1
View File
@@ -1,2 +1,543 @@
// Confirmación de envío de formulario en welcome
document.addEventListener('DOMContentLoaded', function () {
const form = document.querySelector('form[action="/formulario"]');
const btnConfirmar = document.getElementById('btnConfirmarEnvioForm');
if (form && btnConfirmar) {
btnConfirmar.addEventListener('click', function () {
form.submit();
});
}
// Confirmación de envío de formulario en dashboard cliente
const formCliente = document.querySelector('form[action="/cliente/formulario"]');
const btnConfirmarCliente = document.getElementById('btnConfirmarEnvioFormCliente');
if (formCliente && btnConfirmarCliente) {
btnConfirmarCliente.addEventListener('click', function () {
formCliente.submit();
});
}
});
import './bootstrap';
import 'bootstrap';
import * as bootstrap from 'bootstrap';
import html2canvas from 'html2canvas';
import { Calendar } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';
import esLocale from '@fullcalendar/core/locales/es';
window.html2canvas = html2canvas;
import timeGridPlugin from '@fullcalendar/timegrid';
window.FullCalendar = { Calendar };
window.FullCalendarPlugins = {
dayGridPlugin,
interactionPlugin,
timeGridPlugin,
};
window.FullCalendarLocales = {
es: esLocale,
};
window.bootstrap = bootstrap;
document.addEventListener('DOMContentLoaded', function () {
const carouseles = document.querySelectorAll('.carousel');
if (!carouseles.length) {
return;
}
carouseles.forEach((carouselEl) => {
const totalItems = carouselEl.querySelectorAll('.carousel-item').length;
if (totalItems <= 1) {
return;
}
const instancia = bootstrap.Carousel.getOrCreateInstance(carouselEl, {
interval: 10000,
ride: 'carousel',
pause: 'hover',
wrap: true,
touch: true,
});
instancia.cycle();
});
});
const requiereSidebarLateral =
window.location.pathname.startsWith('/administrador') ||
window.location.pathname.startsWith('/profesional');
const mainSidebarTarget = requiereSidebarLateral ? document.querySelector('main') : null;
const limpiarEstadoPendingSidebar = () => {
document.documentElement.classList.remove('sidebar-nav-pending');
mainSidebarTarget?.classList.remove('sidebar-main-pending');
};
if (requiereSidebarLateral) {
if (mainSidebarTarget) {
mainSidebarTarget.classList.add('admin-main-content', 'sidebar-main-pending');
}
document.documentElement.classList.add('sidebar-nav-pending');
window.setTimeout(() => {
limpiarEstadoPendingSidebar();
}, 900);
}
const asegurarFavicon = () => {
const faviconHref = '/favicon.ico?v=20260403';
const rels = ['icon', 'shortcut icon', 'apple-touch-icon'];
rels.forEach((rel) => {
let link = document.head?.querySelector(`link[rel="${rel}"]`);
if (!link) {
link = document.createElement('link');
link.setAttribute('rel', rel);
document.head?.appendChild(link);
}
link.setAttribute('type', 'image/x-icon');
link.setAttribute('href', faviconHref);
});
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', asegurarFavicon, { once: true });
} else {
asegurarFavicon();
}
document.addEventListener('DOMContentLoaded', function () {
const mapWidgets = document.querySelectorAll('[data-map-widget]');
if (!mapWidgets.length) {
return;
}
const updateMapWidget = (widget) => {
const iframe = widget.querySelector('[data-map-embed]');
const fallback = widget.querySelector('[data-map-fallback]');
if (!iframe) {
return;
}
const mapSrc = iframe.getAttribute('data-map-src') || '';
const offline = !navigator.onLine;
if (offline) {
iframe.classList.add('d-none');
fallback?.classList.remove('d-none');
return;
}
if (mapSrc && iframe.getAttribute('src') !== mapSrc) {
iframe.setAttribute('src', mapSrc);
}
iframe.classList.remove('d-none');
fallback?.classList.add('d-none');
};
mapWidgets.forEach((widget) => {
const iframe = widget.querySelector('[data-map-embed]');
if (iframe) {
iframe.addEventListener('error', () => {
iframe.classList.add('d-none');
widget.querySelector('[data-map-fallback]')?.classList.remove('d-none');
});
}
updateMapWidget(widget);
});
window.addEventListener('online', () => {
mapWidgets.forEach(updateMapWidget);
});
window.addEventListener('offline', () => {
mapWidgets.forEach(updateMapWidget);
});
});
const STORAGE_KEY_NOTIF_CERRADAS = 'notif_cerradas_profesional';
const STORAGE_KEY_NOTIF_VISTAS = 'notif_vistas_profesional';
function getNotificacionesCerradas() {
try {
return JSON.parse(localStorage.getItem(STORAGE_KEY_NOTIF_CERRADAS) || '[]');
} catch {
return [];
}
}
function guardarNotificacionesCerradas(claves) {
localStorage.setItem(STORAGE_KEY_NOTIF_CERRADAS, JSON.stringify(claves));
}
function getNotificacionesVistas() {
try {
const vistas = JSON.parse(localStorage.getItem(STORAGE_KEY_NOTIF_VISTAS) || '[]');
return Array.from(new Set([...(Array.isArray(vistas) ? vistas : []), ...getNotificacionesCerradas()]));
} catch {
return getNotificacionesCerradas();
}
}
function guardarNotificacionesVistas(claves) {
localStorage.setItem(STORAGE_KEY_NOTIF_VISTAS, JSON.stringify(Array.from(new Set(claves))));
}
function marcarNotificacionesComoVistas(clavesActivas) {
if (!Array.isArray(clavesActivas) || !clavesActivas.length) {
return;
}
const vistasActuales = getNotificacionesVistas();
guardarNotificacionesVistas([...vistasActuales, ...clavesActivas]);
}
function podarNotificacionesVistas(clavesActivas) {
const vistas = getNotificacionesVistas();
if (!vistas.length) {
return [];
}
const vistasVigentes = vistas.filter((clave) => clavesActivas.includes(clave));
if (vistasVigentes.length !== vistas.length) {
guardarNotificacionesVistas(vistasVigentes);
}
return vistasVigentes;
}
function actualizarBadgeNotificaciones() {
const enlace = document.querySelector('a[href="/profesional/notificaciones"][data-notificaciones-claves]');
if (!enlace) {
return;
}
const badge = enlace.querySelector('.js-notificaciones-badge');
if (!badge) {
return;
}
let clavesActivas = [];
try {
clavesActivas = JSON.parse(enlace.dataset.notificacionesClaves || '[]');
} catch {
clavesActivas = [];
}
if (window.location.pathname === '/profesional/notificaciones') {
marcarNotificacionesComoVistas(clavesActivas);
}
const vistas = podarNotificacionesVistas(clavesActivas);
const tienePendientes = clavesActivas.some((clave) => !vistas.includes(clave));
badge.classList.toggle('d-none', !tienePendientes);
}
window.actualizarBadgeNotificaciones = actualizarBadgeNotificaciones;
actualizarBadgeNotificaciones();
document.addEventListener('DOMContentLoaded', function () {
actualizarBadgeNotificaciones();
});
document.addEventListener('DOMContentLoaded', function () {
const soloNumeros = ['cuil', 'celular', 'telefono'];
const soloLetras = ['nombre', 'apellido'];
const normalizarIdentificador = (valor) => String(valor || '').trim().toLowerCase();
const esCampo = (input, listaCampos) => {
const nombre = normalizarIdentificador(input.name);
const id = normalizarIdentificador(input.id);
return listaCampos.includes(nombre) || listaCampos.includes(id);
};
const esNumerico = (char) => /\d/.test(char);
const esLetra = (char) => /[A-Za-zÁÉÍÓÚáéíóúÑñ\s]/.test(char);
const inputs = Array.from(document.querySelectorAll('input'));
inputs.forEach((input) => {
if (esCampo(input, soloNumeros)) {
input.setAttribute('inputmode', 'numeric');
input.setAttribute('pattern', '[0-9]+');
input.addEventListener('beforeinput', function (e) {
if (!esNumerico(e.data) && e.data !== null) {
e.preventDefault();
}
});
input.addEventListener('input', function () {
input.value = input.value.replace(/\D/g, '');
});
}
if (esCampo(input, soloLetras)) {
input.setAttribute('pattern', '[A-Za-zÁÉÍÓÚáéíóúÑñ\\s]+');
input.addEventListener('beforeinput', function (e) {
if (!esLetra(e.data) && e.data !== null) {
e.preventDefault();
}
});
input.addEventListener('input', function () {
input.value = input.value.replace(/[^A-Za-zÁÉÍÓÚáéíóúÑñ\s]/g, '');
});
}
if (input.type === 'password') {
const formAction = String(input.form?.getAttribute('action') || '');
const nombre = normalizarIdentificador(input.name);
const esLogin = formAction.includes('/login/');
const esContraActual = nombre === 'contra_actual' || nombre === 'contra_actual_secreta';
if (!esLogin && !esContraActual) {
input.setAttribute('minlength', '6');
}
}
});
});
document.addEventListener('DOMContentLoaded', function () {
if (!window.location.pathname.startsWith('/administrador')) {
return;
}
const header = document.querySelector('.app-navbar');
if (!header) {
limpiarEstadoPendingSidebar();
return;
}
const navbarContainer = header.querySelector('.navbar .container');
const collapse = header.querySelector('#menuPrincipal');
const logout = (collapse || header).querySelector('a[href="/logout"]');
const adminMenuList = (collapse || header).querySelector('ul.navbar-nav');
if (adminMenuList && !adminMenuList.querySelector('a[href="/administrador/ayuda"]')) {
const ayudaItem = document.createElement('li');
ayudaItem.className = 'nav-item';
const ayudaLink = document.createElement('a');
ayudaLink.className = 'btn app-navbar-link';
ayudaLink.setAttribute('href', '/administrador/ayuda');
ayudaLink.setAttribute('title', 'Ayuda para administrador');
ayudaLink.setAttribute('aria-label', 'Ayuda para administrador');
ayudaLink.textContent = '¿Necesitas Ayuda?';
ayudaItem.appendChild(ayudaLink);
adminMenuList.appendChild(ayudaItem);
}
const menuLinks = Array.from((collapse || header).querySelectorAll('ul .app-navbar-link'));
if (menuLinks.length && !document.querySelector('.admin-sidebar')) {
const aside = document.createElement('aside');
aside.className = 'admin-sidebar';
aside.setAttribute('aria-label', 'Menu administrador');
const nav = document.createElement('nav');
nav.className = 'admin-sidebar-nav';
const subitemsContenido = [
{ label: 'Quienes Somos', href: '/administrador/contenido/quienes-somos' },
{ label: 'Profesiones', href: '/administrador/contenido/profesiones' },
{ label: 'Servicios', href: '/administrador/contenido/servicios' },
{ label: 'Asistente Virtual', href: '/administrador/contenido/asistente-virtual/ver-faqs' },
];
menuLinks.forEach((link) => {
const href = link.getAttribute('href') || '';
if (href === '/administrador/contenido-web') {
const enContenido =
window.location.pathname.startsWith('/administrador/contenido/') ||
window.location.pathname === '/administrador/contenido-web' ||
window.location.pathname === '/administrador/asistente-consultas';
const details = document.createElement('details');
details.className = 'admin-sidebar-details';
if (enContenido) {
details.setAttribute('open', '');
}
const summary = document.createElement('summary');
summary.className = 'btn app-navbar-link admin-sidebar-link admin-sidebar-summary';
if (enContenido) {
summary.classList.add('active');
}
summary.textContent = 'Contenido';
details.appendChild(summary);
const subitemsContenidoConConsultas = [
...subitemsContenido,
{ label: 'Consultas sin respuesta', href: '/administrador/asistente-consultas' },
];
subitemsContenidoConConsultas.forEach(({ label, href: subhref }) => {
const a = document.createElement('a');
a.className = 'btn app-navbar-link admin-sidebar-link admin-sidebar-sublink';
a.setAttribute('href', subhref);
if (window.location.pathname === subhref) {
a.classList.add('active');
}
a.textContent = label;
details.appendChild(a);
});
nav.appendChild(details);
return;
}
const item = link.cloneNode(true);
item.classList.add('admin-sidebar-link');
const esRutaActiva =
href &&
href !== '#top' &&
(window.location.pathname === href || window.location.pathname.startsWith(`${href}/`));
const esMisDatosEnDashboard = href === '#top' && window.location.pathname === '/administrador/dashboard';
if (esRutaActiva || esMisDatosEnDashboard) {
item.classList.add('active');
}
nav.appendChild(item);
});
aside.appendChild(nav);
header.insertAdjacentElement('afterend', aside);
}
if (collapse) {
const ul = collapse.querySelector('ul');
if (ul) {
ul.remove();
}
collapse.classList.remove('collapse', 'navbar-collapse');
collapse.removeAttribute('id');
collapse.classList.add('d-flex', 'ms-auto', 'align-items-center');
}
const toggler = header.querySelector('.navbar-toggler');
if (toggler) {
toggler.remove();
}
if (logout && navbarContainer) {
logout.classList.add('btn', 'app-navbar-link');
navbarContainer.appendChild(logout);
}
const actualizarOffsetSidebar = () => {
const alturaNavbar = Math.ceil(header.offsetHeight || 74);
document.documentElement.style.setProperty('--admin-navbar-height', `${alturaNavbar}px`);
};
actualizarOffsetSidebar();
window.addEventListener('resize', actualizarOffsetSidebar);
limpiarEstadoPendingSidebar();
});
document.addEventListener('DOMContentLoaded', function () {
if (!window.location.pathname.startsWith('/profesional')) {
return;
}
const header = document.querySelector('.app-navbar');
if (!header) {
limpiarEstadoPendingSidebar();
return;
}
const navbarContainer = header.querySelector('.navbar .container');
const collapse = header.querySelector('#menuPrincipal');
const logout = (collapse || header).querySelector('a[href="/logout"]');
const menuList = (collapse || header).querySelector('ul.navbar-nav');
if (menuList && !menuList.querySelector('a[href="/profesional/ayuda"]')) {
const ayudaItem = document.createElement('li');
ayudaItem.className = 'nav-item';
const ayudaLink = document.createElement('a');
ayudaLink.className = 'btn app-navbar-link';
ayudaLink.setAttribute('href', '/profesional/ayuda');
ayudaLink.setAttribute('title', 'Ayuda para profesionales');
ayudaLink.setAttribute('aria-label', 'Ayuda para profesionales');
ayudaLink.textContent = '¿Necesitas Ayuda?';
ayudaItem.appendChild(ayudaLink);
menuList.appendChild(ayudaItem);
}
const menuLinks = Array.from((collapse || header).querySelectorAll('ul .app-navbar-link'));
if (menuLinks.length && !document.querySelector('.admin-sidebar')) {
const aside = document.createElement('aside');
aside.className = 'admin-sidebar';
aside.setAttribute('aria-label', 'Menu profesional');
const nav = document.createElement('nav');
nav.className = 'admin-sidebar-nav';
menuLinks.forEach((link) => {
const href = link.getAttribute('href') || '';
const item = link.cloneNode(true);
item.classList.add('admin-sidebar-link');
const esRutaActiva =
href &&
href !== '#top' &&
(window.location.pathname === href || window.location.pathname.startsWith(`${href}/`));
const esMiAgendaEnDashboard = href === '#top' && window.location.pathname === '/profesional/dashboard';
if (esRutaActiva || esMiAgendaEnDashboard) {
item.classList.add('active');
}
nav.appendChild(item);
});
aside.appendChild(nav);
header.insertAdjacentElement('afterend', aside);
}
if (collapse) {
const ul = collapse.querySelector('ul');
if (ul) {
ul.remove();
}
collapse.classList.remove('collapse', 'navbar-collapse');
collapse.removeAttribute('id');
collapse.classList.add('d-flex', 'ms-auto', 'align-items-center');
}
const toggler = header.querySelector('.navbar-toggler');
if (toggler) {
toggler.remove();
}
if (logout && navbarContainer) {
logout.classList.add('btn', 'app-navbar-link');
navbarContainer.appendChild(logout);
}
const actualizarOffsetSidebar = () => {
const alturaNavbar = Math.ceil(header.offsetHeight || 74);
document.documentElement.style.setProperty('--admin-navbar-height', `${alturaNavbar}px`);
};
actualizarOffsetSidebar();
window.addEventListener('resize', actualizarOffsetSidebar);
limpiarEstadoPendingSidebar();
});