feat: 6 nuevas funcionalidades - notas obligatorias turno, reporte enfermero, plantilla WhatsApp, adjuntar orden modal, roles solo lectura, fix valores copago

This commit is contained in:
Lizandro Guarnizo
2026-04-02 09:41:49 -05:00
parent 052a263659
commit d8ceb3f822
14 changed files with 2794 additions and 49 deletions
+86 -1
View File
@@ -594,6 +594,29 @@ if (!isUserLoggedIn()) {
.notification-toast.urgent { border-left: 4px solid #e74c3c; }
/* Badge de usuario bloqueado en el header */
#blocked-indicator {
display: none;
margin-left: 8px;
background: #dc3545;
color: #fff;
font-size: 10px;
font-weight: 700;
padding: 2px 8px;
border-radius: 10px;
vertical-align: middle;
letter-spacing: .3px;
}
/* Botón bloquear — estados */
#block-user-btn.is-blocked { background: rgba(220,53,69,.18); border-color: #dc3545; color: #dc3545; }
#block-user-btn.is-blocked:hover { background: rgba(220,53,69,.28); }
/* Ítem bloqueado en sidebar */
.conversation-item.user-blocked { opacity: .6; }
.conversation-item.user-blocked .conversation-name::after {
content: ' 🚫';
font-size: 10px;
}
/* Visual destacado para notificaciones de tipo "attention" (ej. usuario subió documentos) */
.notification-toast.attention {
background: linear-gradient(90deg, #fff7e6, #fff3e0);
@@ -912,6 +935,7 @@ if (!isUserLoggedIn()) {
<div class="chat-header-info">
<h6 id="chat-name"><span id="chat-name-text">Usuario</span> <button class="btn btn-sm btn-link" id="edit-user-btn" title="Editar usuario"><i class="fas fa-user-edit"></i></button>
<span id="hold-indicator" style="display:none; margin-left:8px; color:#b85; font-weight:600">EN ESPERA</span>
<span id="blocked-indicator"><i class="fas fa-ban me-1"></i>BLOQUEADO</span>
<span id="terms-pending-badge" title="Este usuario aún no ha aceptado los Términos y Condiciones"
style="display:none; margin-left:8px; background:#dc3545; color:#fff; font-size:10px; font-weight:600; padding:2px 7px; border-radius:10px; vertical-align:middle; cursor:default;">
<i class="fas fa-file-contract me-1"></i>T&amp;C Pendiente
@@ -948,6 +972,10 @@ if (!isUserLoggedIn()) {
<button class="btn btn-sm btn-success" id="schedule-reminder-btn" title="Programar recordatorio">
<i class="fas fa-calendar-plus"></i>
</button>
<!-- Botón bloquear/desbloquear usuario -->
<button class="btn btn-sm btn-outline-danger" id="block-user-btn" title="Bloquear usuario">
<i class="fas fa-ban"></i>
</button>
<!-- Botón actualizar mensajes -->
<button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
@@ -2762,12 +2790,13 @@ if (!isUserLoggedIn()) {
// different color if has unread and not active
const unreadClass = (conv.unread_count && conv.unread_count > 0 && !isActive) ? 'unread' : '';
const attentionClass = conv.advisor_requested ? ' attention' : '';
const blockedClass = conv.user_status === 'blocked' ? ' user-blocked' : '';
// Escapar caracteres especiales en nombre y teléfono para no romper el onclick
const safeName = (conv.name || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '&quot;').replace(/`/g, '\\`');
const safePhone = (conv.phone_number || '').replace(/\\/g, '\\\\').replace(/'/g, "\\'");
return `
<div class="conversation-item ${isActive} ${unreadClass}${attentionClass}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${safeName}', '${safePhone}')">
<div class="conversation-item ${isActive} ${unreadClass}${attentionClass}${blockedClass}" data-user-id="${conv.user_id}" onclick="chat.openConversation(${conv.user_id}, '${safeName}', '${safePhone}')">
<div class="conversation-avatar-wrapper">
${avatar}
</div>
@@ -3260,6 +3289,62 @@ if (!isUserLoggedIn()) {
}
};
}
// ── Botón Bloquear / Desbloquear ────────────────────────────
const blockBtn = document.getElementById('block-user-btn');
const blockedIndicator = document.getElementById('blocked-indicator');
if (blockBtn) {
const isBlocked = conv.user_status === 'blocked';
// Actualizar apariencia
blockBtn.classList.toggle('is-blocked', isBlocked);
blockBtn.title = isBlocked ? 'Desbloquear usuario' : 'Bloquear usuario';
blockBtn.innerHTML = isBlocked
? '<i class="fas fa-lock-open"></i>'
: '<i class="fas fa-ban"></i>';
if (blockedIndicator) {
blockedIndicator.style.display = isBlocked ? 'inline' : 'none';
}
blockBtn.onclick = async () => {
const currentlyBlocked = conv.user_status === 'blocked';
const action = currentlyBlocked ? 'desbloquear' : 'bloquear';
if (!confirm(`¿Confirmas ${action} a este usuario?\n${currentlyBlocked ? 'Podrá volver a interactuar con el bot.' : 'El bot ignorará sus mensajes.'}`)) return;
blockBtn.disabled = true;
try {
const resp = await fetch('api/block_user.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ user_id: userId, blocked: !currentlyBlocked }),
cache: 'no-store'
});
if (resp.status === 401) { alert('Sesión expirada.'); return; }
const json = await resp.json();
if (json && json.success) {
conv.user_status = json.status;
const nowBlocked = json.blocked;
blockBtn.classList.toggle('is-blocked', nowBlocked);
blockBtn.title = nowBlocked ? 'Desbloquear usuario' : 'Bloquear usuario';
blockBtn.innerHTML = nowBlocked
? '<i class="fas fa-lock-open"></i>'
: '<i class="fas fa-ban"></i>';
if (blockedIndicator) {
blockedIndicator.style.display = nowBlocked ? 'inline' : 'none';
}
// Actualizar ítem en sidebar
const sideItem = document.querySelector(`.conversation-item[data-user-id="${userId}"]`);
if (sideItem) sideItem.classList.toggle('user-blocked', nowBlocked);
showAlert(nowBlocked ? '🚫 Usuario bloqueado' : '✅ Usuario desbloqueado', nowBlocked ? 'warning' : 'success');
} else {
alert('Error: ' + (json && json.error ? json.error : 'Error desconocido'));
}
} catch (e) {
console.error('Error toggling block', e);
alert('Error al cambiar estado de bloqueo');
} finally {
blockBtn.disabled = false;
}
};
}
}
// Cargar mensajes (con paginación)