From bbfe0417df8445331a7c280a2b36df30b655d545 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Sat, 14 Mar 2026 12:52:45 -0500 Subject: [PATCH] up --- api/lab/upload_orden.php | 88 +++++++++++++++ conversations.php | 225 ++++++++++++++++++++++++++++++++------- 2 files changed, 276 insertions(+), 37 deletions(-) create mode 100644 api/lab/upload_orden.php diff --git a/api/lab/upload_orden.php b/api/lab/upload_orden.php new file mode 100644 index 0000000..0209d08 --- /dev/null +++ b/api/lab/upload_orden.php @@ -0,0 +1,88 @@ + false, 'error' => 'Método no permitido']); + exit; +} + +if (empty($_FILES['file']) || $_FILES['file']['error'] !== UPLOAD_ERR_OK) { + $errCode = $_FILES['file']['error'] ?? -1; + echo json_encode(['success' => false, 'error' => 'No se recibió ningún archivo (código ' . $errCode . ')']); + exit; +} + +$file = $_FILES['file']; + +// Validar tamaño máximo: 10 MB +if ($file['size'] > 10 * 1024 * 1024) { + echo json_encode(['success' => false, 'error' => 'El archivo supera el tamaño máximo permitido (10 MB)']); + exit; +} + +// Validar tipo MIME real (no confiar solo en la extensión del cliente) +$allowedMimes = [ + 'image/jpeg', + 'image/png', + 'image/gif', + 'image/webp', + 'application/pdf', + 'application/msword', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', +]; + +$finfo = finfo_open(FILEINFO_MIME_TYPE); +$mime = finfo_file($finfo, $file['tmp_name']); +finfo_close($finfo); + +if (!in_array($mime, $allowedMimes, true)) { + echo json_encode(['success' => false, 'error' => 'Tipo de archivo no permitido (' . $mime . ')']); + exit; +} + +// Generar nombre único y seguro +$origExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); +$origExt = preg_replace('/[^a-z0-9]/', '', $origExt); +if (!$origExt) $origExt = 'bin'; + +$newName = 'orden_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $origExt; + +$uploadDir = realpath(__DIR__ . '/../../uploads/media'); +if (!$uploadDir) { + // Intentar crear el directorio si no existe + @mkdir(__DIR__ . '/../../uploads/media', 0755, true); + $uploadDir = realpath(__DIR__ . '/../../uploads/media'); +} + +if (!$uploadDir || !is_writable($uploadDir)) { + echo json_encode(['success' => false, 'error' => 'El directorio de uploads no está disponible']); + exit; +} + +$destPath = $uploadDir . DIRECTORY_SEPARATOR . $newName; + +if (!move_uploaded_file($file['tmp_name'], $destPath)) { + echo json_encode(['success' => false, 'error' => 'Error al guardar el archivo en el servidor']); + exit; +} + +echo json_encode([ + 'success' => true, + 'local_file' => $newName, + 'file_name' => $file['name'], + 'file_size' => $file['size'], + 'mime' => $mime, +]); diff --git a/conversations.php b/conversations.php index a232ef4..2eb1e69 100644 --- a/conversations.php +++ b/conversations.php @@ -838,6 +838,22 @@ if (!isset($_SESSION['user_id'])) { .conversation-item.unread { background: rgba(255, 248, 220, 0.9); } .unread-badge { margin-left: 8px; font-size: 12px; } + /* Tooltip «Copiado» al hacer doble clic en un mensaje */ + .copy-feedback { + position: absolute; + top: -26px; left: 50%; + transform: translateX(-50%); + background: rgba(0,0,0,0.72); + color: #fff; + padding: 3px 10px; + border-radius: 12px; + font-size: 12px; font-weight: 600; + white-space: nowrap; pointer-events: none; + animation: cfadeIn .15s ease; + z-index: 100; + } + @keyframes cfadeIn { from { opacity:0; transform: translateX(-50%) translateY(4px); } to { opacity:1; transform: translateX(-50%) translateY(0); } } + @@ -2100,6 +2116,34 @@ if (!isset($_SESSION['user_id'])) { // Expose small helper to other methods this._updateSendMicVisibility = updateSendMicVisibility; + // Doble clic en burbuja de mensaje → copiar texto al portapapeles + const chatConvContainer = document.getElementById('chat-conversations'); + if (chatConvContainer) { + chatConvContainer.addEventListener('dblclick', (e) => { + const bubble = e.target.closest('.message-bubble'); + if (!bubble) return; + const contentEl = bubble.querySelector('.message-content'); + if (!contentEl) return; + const text = (contentEl.innerText || contentEl.textContent || '').trim(); + if (!text) return; + const doFeedback = () => this._showCopyToast(bubble); + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(text).then(doFeedback).catch(() => { + try { document.execCommand('copy'); doFeedback(); } catch(e2) {} + }); + } else { + try { + const sel = window.getSelection(); + const range = document.createRange(); + range.selectNodeContents(contentEl); + sel.removeAllRanges(); sel.addRange(range); + document.execCommand('copy'); + sel.removeAllRanges(); + doFeedback(); + } catch(e2) {} + } + }); + } // File input selected -> show send button const fileInput = document.getElementById('file-input'); @@ -4002,7 +4046,7 @@ if (!isset($_SESSION['user_id'])) {
- ${(msg.message_type === 'image' && msg.direction !== 'outgoing') ? `` : ''}${window._labAddrBtn ? window._labAddrBtn(msg, chat ? chat.currentConversationId : 0) : ''} + ${(['image','document','video','audio','sticker'].includes(msg.message_type) && msg.direction !== 'outgoing') ? `` : ''}${window._labAddrBtn ? window._labAddrBtn(msg, chat ? chat.currentConversationId : 0) : ''}
${time} ${msg.direction === 'outgoing' ? `${statusIcon}` : ''}
@@ -5264,6 +5308,16 @@ if (!isset($_SESSION['user_id'])) { container.scrollTop = container.scrollHeight; } + _showCopyToast(bubble) { + const existing = bubble.querySelector('.copy-feedback'); + if (existing) existing.remove(); + const tip = document.createElement('span'); + tip.className = 'copy-feedback'; + tip.textContent = '✓ Copiado'; + bubble.appendChild(tip); + setTimeout(() => { if (tip.parentNode) tip.remove(); }, 1800); + } + // Insertar mensaje saliente en la vista inmediatamente (simular envío) addMessageToView(content, type = 'outgoing', options = {}) { const msg = { @@ -6543,13 +6597,30 @@ if (!isset($_SESSION['user_id'])) {