subir mas de un archivo, reprogramados
This commit is contained in:
+108
-75
@@ -1,99 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* API: Subir archivo de orden médica desde el modal de agendamiento
|
||||
* API: Subir archivos de orden médica desde el modal de agendamiento
|
||||
*
|
||||
* POST (multipart/form-data) con campo 'file'
|
||||
* Devuelve: { success, local_file, file_name, file_size, mime }
|
||||
* POST (multipart/form-data):
|
||||
* - files[] : uno o varios archivos (también acepta 'file' singular para retrocompat)
|
||||
* - domicilio_id : (opcional) vincula los archivos a un domicilio creando registros en lab_ordenes_medicas
|
||||
* - paciente_id : (opcional, requerido si domicilio_id se usa)
|
||||
*
|
||||
* Devuelve: { success, archivos: [{local_file, file_name, file_size, mime, orden_id?}] }
|
||||
* + local_file (retrocompat: primer archivo)
|
||||
*/
|
||||
require_once __DIR__ . '/../../config/config.php';
|
||||
require_once __DIR__ . '/../../classes/Database.php';
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireMethod('POST');
|
||||
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
|
||||
requireAuthentication();
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
|
||||
http_response_code(405);
|
||||
echo json_encode(['success' => 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',
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
'application/pdf' => 'pdf',
|
||||
'application/msword' => 'doc',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
|
||||
'application/vnd.ms-excel' => 'xls',
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => 'xlsx',
|
||||
'text/plain' => 'txt',
|
||||
'text/csv' => 'csv',
|
||||
];
|
||||
|
||||
$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) {
|
||||
$mimeToExt = [
|
||||
'image/jpeg' => 'jpg',
|
||||
'image/png' => 'png',
|
||||
'image/gif' => 'gif',
|
||||
'image/webp' => 'webp',
|
||||
'application/pdf' => 'pdf',
|
||||
'application/msword' => 'doc',
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => 'docx',
|
||||
// Normalizar: acepta files[] o file (retrocompat)
|
||||
$rawFiles = [];
|
||||
if (!empty($_FILES['files']['name'][0])) {
|
||||
// files[] (múltiples)
|
||||
$count = count($_FILES['files']['name']);
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$rawFiles[] = [
|
||||
'name' => $_FILES['files']['name'][$i],
|
||||
'tmp_name' => $_FILES['files']['tmp_name'][$i],
|
||||
'error' => $_FILES['files']['error'][$i],
|
||||
'size' => $_FILES['files']['size'][$i],
|
||||
];
|
||||
}
|
||||
} elseif (!empty($_FILES['file']['tmp_name'])) {
|
||||
// file (singular, retrocompat)
|
||||
$rawFiles[] = [
|
||||
'name' => $_FILES['file']['name'],
|
||||
'tmp_name' => $_FILES['file']['tmp_name'],
|
||||
'error' => $_FILES['file']['error'],
|
||||
'size' => $_FILES['file']['size'],
|
||||
];
|
||||
$origExt = $mimeToExt[$mime] ?? 'bin';
|
||||
}
|
||||
|
||||
$newName = 'orden_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $origExt;
|
||||
if (empty($rawFiles)) {
|
||||
jsonError('No se recibió ningún archivo');
|
||||
}
|
||||
|
||||
$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;
|
||||
jsonError('El directorio de uploads no está disponible');
|
||||
}
|
||||
|
||||
$destPath = $uploadDir . DIRECTORY_SEPARATOR . $newName;
|
||||
$domicilioId = isset($_POST['domicilio_id']) ? (int)$_POST['domicilio_id'] : 0;
|
||||
$pacienteId = isset($_POST['paciente_id']) ? (int)$_POST['paciente_id'] : 0;
|
||||
$om = ($domicilioId && $pacienteId) ? new OrdenMedica() : null;
|
||||
$adminId = adminId();
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
echo json_encode(['success' => false, 'error' => 'Error al guardar el archivo en el servidor']);
|
||||
exit;
|
||||
$archivos = [];
|
||||
|
||||
foreach ($rawFiles as $file) {
|
||||
if ($file['error'] !== UPLOAD_ERR_OK) {
|
||||
continue; // saltar archivos con error de upload
|
||||
}
|
||||
|
||||
// Validar tamaño máximo: 10 MB
|
||||
if ($file['size'] > 10 * 1024 * 1024) {
|
||||
jsonError('El archivo "' . basename($file['name']) . '" supera el límite de 10 MB');
|
||||
}
|
||||
|
||||
// Validar tipo MIME real
|
||||
$finfo = finfo_open(FILEINFO_MIME_TYPE);
|
||||
$mime = finfo_file($finfo, $file['tmp_name']);
|
||||
finfo_close($finfo);
|
||||
|
||||
if (!array_key_exists($mime, $allowedMimes)) {
|
||||
jsonError('Tipo de archivo no permitido: ' . $mime);
|
||||
}
|
||||
|
||||
// Generar nombre único y seguro
|
||||
$origExt = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
|
||||
$origExt = preg_replace('/[^a-z0-9]/', '', $origExt);
|
||||
if (!$origExt) {
|
||||
$origExt = $allowedMimes[$mime];
|
||||
}
|
||||
|
||||
$newName = 'orden_' . date('Ymd_His') . '_' . bin2hex(random_bytes(4)) . '.' . $origExt;
|
||||
$destPath = $uploadDir . DIRECTORY_SEPARATOR . $newName;
|
||||
|
||||
if (!move_uploaded_file($file['tmp_name'], $destPath)) {
|
||||
jsonError('Error al guardar el archivo en el servidor');
|
||||
}
|
||||
|
||||
$ordenId = null;
|
||||
if ($om) {
|
||||
// Crear registro en lab_ordenes_medicas vinculado al domicilio
|
||||
$ordenId = $om->crear([
|
||||
'paciente_id' => $pacienteId,
|
||||
'domicilio_id' => $domicilioId,
|
||||
'local_file' => $newName,
|
||||
], $adminId);
|
||||
}
|
||||
|
||||
$archivos[] = [
|
||||
'local_file' => $newName,
|
||||
'file_name' => $file['name'],
|
||||
'file_size' => $file['size'],
|
||||
'mime' => $mime,
|
||||
'orden_id' => $ordenId,
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'success' => true,
|
||||
'local_file' => $newName,
|
||||
'file_name' => $file['name'],
|
||||
'file_size' => $file['size'],
|
||||
'mime' => $mime,
|
||||
if (empty($archivos)) {
|
||||
jsonError('No se pudo procesar ningún archivo');
|
||||
}
|
||||
|
||||
jsonOk([
|
||||
'archivos' => $archivos,
|
||||
'local_file' => $archivos[0]['local_file'], // retrocompat
|
||||
]);
|
||||
|
||||
+37
-11
@@ -58,6 +58,7 @@ $hoy = date('Y-m-d');
|
||||
.domcard.estado-en_domicilio { border-color:#20c997; }
|
||||
.domcard.estado-completado { border-color:#198754; opacity:.75; }
|
||||
.domcard.estado-cancelado { border-color:#dc3545; opacity:.6; }
|
||||
.domcard.estado-reprogramado { border-color:#6f42c1; opacity:.65; }
|
||||
|
||||
/* ── Badges estado ── */
|
||||
.badge-programado { background:#6c757d; }
|
||||
@@ -66,6 +67,7 @@ $hoy = date('Y-m-d');
|
||||
.badge-en_domicilio { background:#20c997; }
|
||||
.badge-completado { background:#198754; }
|
||||
.badge-cancelado { background:#dc3545; }
|
||||
.badge-reprogramado { background:#6f42c1; }
|
||||
|
||||
/* ── Botones acción ── */
|
||||
.btn-accion { font-size:.78rem; padding:.28rem .65rem; border-radius:20px; }
|
||||
@@ -434,6 +436,7 @@ const ESTADOS_LABEL = {
|
||||
en_domicilio: { label:'En domicilio', icon:'🏠', cls:'badge-en_domicilio' },
|
||||
completado: { label:'Completado', icon:'🎉', cls:'badge-completado' },
|
||||
cancelado: { label:'Cancelado', icon:'❌', cls:'badge-cancelado' },
|
||||
reprogramado: { label:'Reprogramado', icon:'📅', cls:'badge-reprogramado' },
|
||||
};
|
||||
|
||||
// Transiciones que el enfermero puede hacer
|
||||
@@ -507,7 +510,7 @@ const portal = {
|
||||
return;
|
||||
}
|
||||
const ACTIVOS = ['programado','confirmado','en_camino','en_domicilio'];
|
||||
const CERRADOS = ['completado','cancelado'];
|
||||
const CERRADOS = ['completado','cancelado','reprogramado'];
|
||||
const sorted = [...this._agenda].sort((a,b) =>
|
||||
(a.hora_programada||'99:99').localeCompare(b.hora_programada||'99:99'));
|
||||
const activos = sorted.filter(i => ACTIVOS.includes(i.domicilio_estado||'programado'));
|
||||
@@ -516,9 +519,12 @@ const portal = {
|
||||
let html = activos.map((item, idx) => this._cardHTML(item, idx === 0)).join('');
|
||||
|
||||
if (cerrados.length) {
|
||||
const secLabel = cerrados.some(i => !['completado','cancelado'].includes(i.domicilio_estado||''))
|
||||
? `Cerrados / Reprogramados (${cerrados.length})`
|
||||
: `Finalizados (${cerrados.length})`;
|
||||
html += `<button class="fin-section-toggle" onclick="toggleFinalizados(this)">
|
||||
<i class="fas fa-check-circle text-success"></i>
|
||||
Finalizados (${cerrados.length})
|
||||
${secLabel}
|
||||
<i class="fas fa-chevron-down fin-chev"></i>
|
||||
</button>
|
||||
<div id="seccion-fin">
|
||||
@@ -1225,14 +1231,16 @@ const formVer = {
|
||||
placeholder="Instrucciones adicionales para el servicio…"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Orden médica / Archivo -->
|
||||
<!-- Orden médica / Archivos -->
|
||||
<div class="mb-2">
|
||||
<label class="form-label small fw-semibold">
|
||||
Orden médica / Archivo <span class="text-muted fw-normal">(opcional)</span>
|
||||
Orden médica / Archivos <span class="text-muted fw-normal">(opcional, puedes elegir varios)</span>
|
||||
</label>
|
||||
<input type="file" id="na-orden-file" accept=".pdf,.jpg,.jpeg,.png"
|
||||
class="form-control form-control-sm">
|
||||
<div id="na-orden-preview" class="mt-1 small text-muted"></div>
|
||||
<input type="file" id="na-orden-file"
|
||||
accept=".pdf,.jpg,.jpeg,.png,.gif,.webp,.doc,.docx,.xls,.xlsx,.txt,.csv"
|
||||
class="form-control form-control-sm" multiple
|
||||
onchange="agendaNueva._previsualizarArchivos(this.files)">
|
||||
<div id="na-orden-preview" class="mt-1 d-flex flex-wrap gap-1"></div>
|
||||
</div>
|
||||
|
||||
<div id="na-error" class="alert alert-danger py-2 d-none small"></div>
|
||||
@@ -1336,6 +1344,9 @@ const agendaNueva = {
|
||||
document.getElementById('na-valor-dom').value = '';
|
||||
document.getElementById('na-valor-cop').value = '';
|
||||
document.getElementById('na-fecha').value = document.getElementById('portal-fecha').value;
|
||||
// Reset archivo adjunto
|
||||
document.getElementById('na-orden-file').value = '';
|
||||
document.getElementById('na-orden-preview').innerHTML = '';
|
||||
document.getElementById('na-sugerencias').classList.add('d-none');
|
||||
document.getElementById('na-paciente-elegido').classList.add('d-none');
|
||||
document.getElementById('na-paciente-buscar').classList.remove('d-none');
|
||||
@@ -1447,6 +1458,20 @@ const agendaNueva = {
|
||||
document.getElementById('na-direccion').focus();
|
||||
},
|
||||
|
||||
_previsualizarArchivos(files) {
|
||||
const prev = document.getElementById('na-orden-preview');
|
||||
prev.innerHTML = '';
|
||||
for (const f of files) {
|
||||
const isImg = f.type.startsWith('image/');
|
||||
const chip = document.createElement('span');
|
||||
chip.className = 'badge bg-light text-dark border';
|
||||
chip.style.cssText = 'font-size:.74rem;max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:inline-block';
|
||||
chip.title = `${f.name} (${(f.size/1024).toFixed(0)} KB)`;
|
||||
chip.innerHTML = `<i class="fas fa-${isImg ? 'image' : 'file'} me-1"></i>${esc(f.name)}`;
|
||||
prev.appendChild(chip);
|
||||
}
|
||||
},
|
||||
|
||||
limpiarPaciente() {
|
||||
document.getElementById('na-paciente-id').value = '';
|
||||
document.getElementById('na-paciente-buscar').value = '';
|
||||
@@ -1568,19 +1593,20 @@ const agendaNueva = {
|
||||
const d = await r.json();
|
||||
if (!d.success) throw new Error(d.error || 'Error al guardar');
|
||||
|
||||
// Subir archivo adjunto si el usuario seleccionó uno
|
||||
// Subir archivos adjuntos si el usuario seleccionó alguno
|
||||
const archivoInput = document.getElementById('na-orden-file');
|
||||
if (archivoInput?.files?.length && d.id) {
|
||||
const fd = new FormData();
|
||||
fd.append('file', archivoInput.files[0]);
|
||||
for (const f of archivoInput.files) fd.append('files[]', f);
|
||||
fd.append('domicilio_id', d.id);
|
||||
fd.append('paciente_id', pacId);
|
||||
try {
|
||||
const rFile = await fetch('api/lab/upload_orden.php', { method: 'POST', body: fd });
|
||||
// Si falla el upload no bloqueamos; el domicilio ya fue creado
|
||||
const dFile = await rFile.json();
|
||||
if (!dFile.success) console.warn('No se pudo adjuntar el archivo:', dFile.error);
|
||||
if (!dFile.success) console.warn('No se pudo adjuntar archivos:', dFile.error);
|
||||
} catch (eFile) {
|
||||
console.warn('Error al subir archivo:', eFile);
|
||||
console.warn('Error al subir archivos:', eFile);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+122
-144
@@ -398,36 +398,21 @@ $domIdParam = (int)($_GET['id'] ?? 0);
|
||||
</h6>
|
||||
<div class="mb-3">
|
||||
<input type="hidden" name="orden_id" id="dom-orden-id-form">
|
||||
<!-- Preview archivo actual o pendiente -->
|
||||
<div id="dom-orden-preview" class="d-flex align-items-center gap-3 p-2 rounded border bg-light mb-2 d-none">
|
||||
<div id="dom-orden-thumb-wrap">
|
||||
<img id="dom-orden-img" src="" alt="Orden" style="max-height:80px;max-width:100px;object-fit:contain;border-radius:4px;display:none">
|
||||
<div id="dom-orden-icon" class="d-none">
|
||||
<i class="fas fa-file-alt fa-3x text-secondary"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-grow-1 overflow-hidden">
|
||||
<div id="dom-orden-fname" class="small fw-semibold text-truncate"></div>
|
||||
<div id="dom-orden-fsize" class="small text-muted"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Lista de chips (archivos ya guardados + nuevos pendientes) -->
|
||||
<div id="dom-orden-list" class="d-flex flex-wrap gap-2 mb-2"></div>
|
||||
<div id="dom-orden-empty" class="small text-muted mb-2">
|
||||
<i class="fas fa-paperclip me-1"></i> Sin orden adjunta
|
||||
<i class="fas fa-paperclip me-1"></i> Sin archivos adjuntos
|
||||
</div>
|
||||
<div class="d-flex gap-2 flex-wrap align-items-center">
|
||||
<label class="btn btn-sm btn-outline-primary mb-0" for="dom-orden-input">
|
||||
<i class="fas fa-upload me-1"></i>
|
||||
<span id="dom-orden-btn-lbl">Adjuntar orden</span>
|
||||
<span id="dom-orden-btn-lbl">Adjuntar archivos</span>
|
||||
</label>
|
||||
<button type="button" class="btn btn-sm btn-outline-danger d-none" id="dom-orden-quitar"
|
||||
onclick="domQuitarOrden()">
|
||||
<i class="fas fa-times me-1"></i> Quitar
|
||||
</button>
|
||||
</div>
|
||||
<input type="file" id="dom-orden-input" class="d-none"
|
||||
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf,.pdf,.doc,.docx"
|
||||
onchange="domSeleccionarOrdenFile(this)">
|
||||
<small class="text-muted d-block mt-1">Imagen, PDF o DOC — máx. 10 MB</small>
|
||||
<input type="file" id="dom-orden-input" class="d-none" multiple
|
||||
accept="image/jpeg,image/png,image/gif,image/webp,application/pdf,.pdf,.doc,.docx,.xls,.xlsx"
|
||||
onchange="domSeleccionarOrdenFiles(this)">
|
||||
<small class="text-muted d-block mt-1">Imágenes, PDF, DOC, XLS — máx. 10 MB por archivo</small>
|
||||
</div>
|
||||
|
||||
<!-- ── Notas ── -->
|
||||
@@ -590,91 +575,85 @@ const modal = new bootstrap.Modal('#modalDomicilio');
|
||||
let _modalRespForm = null;
|
||||
let _modalAsignarEnf = null;
|
||||
|
||||
// ── Estado orden en el modal ───────────────────────────────────────────────
|
||||
let _domOrderFile = null; // File pendiente de subir
|
||||
let _domCurrentLocalFile = null; // local_file actual de la orden
|
||||
let _domRemoveOrder = false; // el usuario quiere desvincularse de la orden
|
||||
// ── Estado órdenes en el modal (multi-archivo) ────────────────────────────
|
||||
let _domNewFiles = []; // File[] nuevos pendientes de subir
|
||||
let _domCurrentFiles = []; // [{orden_id, local_file}] ya guardados
|
||||
let _domRemovedOrdenIds = []; // orden_ids a desvincular al guardar
|
||||
|
||||
function _domResetOrden() {
|
||||
_domOrderFile = null;
|
||||
_domCurrentLocalFile = null;
|
||||
_domRemoveOrder = false;
|
||||
document.getElementById('dom-orden-id-form').value = '';
|
||||
document.getElementById('dom-orden-input').value = '';
|
||||
_domRenderOrdenPreview();
|
||||
function _domResetOrdenes() {
|
||||
_domNewFiles = [];
|
||||
_domCurrentFiles = [];
|
||||
_domRemovedOrdenIds = [];
|
||||
document.getElementById('dom-orden-input').value = '';
|
||||
_domRenderOrdenes();
|
||||
}
|
||||
// Alias para retrocompat con llamadas _domResetOrden()
|
||||
const _domResetOrden = _domResetOrdenes;
|
||||
|
||||
function _domRenderOrdenes() {
|
||||
const list = document.getElementById('dom-orden-list');
|
||||
const empty = document.getElementById('dom-orden-empty');
|
||||
const btn = document.getElementById('dom-orden-btn-lbl');
|
||||
const chipStyle = 'width:72px;height:72px;border:1px solid #dee2e6;border-radius:6px;background:#f8f9fa;display:flex;align-items:center;justify-content:center;overflow:hidden';
|
||||
const delStyle = 'position:absolute;top:-5px;right:-5px;width:18px;height:18px;border-radius:50%;background:#dc3545;color:#fff;border:none;font-size:.7rem;cursor:pointer;display:flex;align-items:center;justify-content:center;padding:0';
|
||||
|
||||
list.innerHTML = '';
|
||||
|
||||
_domCurrentFiles.forEach((f, i) => {
|
||||
const lf = f.local_file || '';
|
||||
const isImg = /\.(jpe?g|png|gif|webp)$/i.test(lf);
|
||||
const thumb = isImg
|
||||
? `<img src="uploads/media/${encodeURIComponent(lf)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
: `<i class="fas fa-file-alt fa-2x text-secondary"></i>`;
|
||||
const fname = lf.split('/').pop();
|
||||
list.insertAdjacentHTML('beforeend', `
|
||||
<div class="position-relative" style="width:72px">
|
||||
<div style="${chipStyle}">${thumb}</div>
|
||||
<span class="d-block text-truncate" style="font-size:.6rem;color:#666;max-width:72px" title="${esc(fname)}">${esc(fname)}</span>
|
||||
<button type="button" onclick="domQuitarExistente(${i})" style="${delStyle}" title="Quitar">×</button>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
_domNewFiles.forEach((f, i) => {
|
||||
const isImg = f.type.startsWith('image/');
|
||||
const thumb = isImg
|
||||
? `<img src="${URL.createObjectURL(f)}" style="width:100%;height:100%;object-fit:cover">`
|
||||
: `<i class="fas fa-file-alt fa-2x text-secondary"></i>`;
|
||||
list.insertAdjacentHTML('beforeend', `
|
||||
<div class="position-relative" style="width:72px">
|
||||
<div style="${chipStyle}">${thumb}</div>
|
||||
<span class="d-block text-truncate" style="font-size:.6rem;color:#666;max-width:72px" title="${esc(f.name)}">${esc(f.name)}</span>
|
||||
<span style="font-size:.55rem;background:#ffc107;color:#000;border-radius:3px;padding:0 3px">nuevo</span>
|
||||
<button type="button" onclick="domQuitarNuevo(${i})" style="${delStyle}" title="Quitar">×</button>
|
||||
</div>`);
|
||||
});
|
||||
|
||||
const total = _domCurrentFiles.length + _domNewFiles.length;
|
||||
empty.classList.toggle('d-none', total > 0);
|
||||
btn.textContent = total > 0 ? 'Agregar más archivos' : 'Adjuntar archivos';
|
||||
}
|
||||
|
||||
function _domRenderOrdenPreview() {
|
||||
const preview = document.getElementById('dom-orden-preview');
|
||||
const empty = document.getElementById('dom-orden-empty');
|
||||
const img = document.getElementById('dom-orden-img');
|
||||
const icon = document.getElementById('dom-orden-icon');
|
||||
const fname = document.getElementById('dom-orden-fname');
|
||||
const fsize = document.getElementById('dom-orden-fsize');
|
||||
const btnQuitar = document.getElementById('dom-orden-quitar');
|
||||
const btnLbl = document.getElementById('dom-orden-btn-lbl');
|
||||
|
||||
if (_domOrderFile) {
|
||||
// Archivo local seleccionado aún no subido
|
||||
const isImg = _domOrderFile.type.startsWith('image/');
|
||||
if (isImg) {
|
||||
img.src = URL.createObjectURL(_domOrderFile);
|
||||
img.style.display = '';
|
||||
icon.classList.add('d-none');
|
||||
} else {
|
||||
img.style.display = 'none';
|
||||
icon.classList.remove('d-none');
|
||||
function domSeleccionarOrdenFiles(input) {
|
||||
for (const file of Array.from(input.files)) {
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert(`"${file.name}" supera los 10 MB permitidos`);
|
||||
continue;
|
||||
}
|
||||
fname.textContent = _domOrderFile.name;
|
||||
fsize.textContent = (_domOrderFile.size / 1024).toFixed(1) + ' KB — pendiente de guardar';
|
||||
preview.classList.remove('d-none');
|
||||
empty.classList.add('d-none');
|
||||
btnQuitar.classList.remove('d-none');
|
||||
btnLbl.textContent = 'Cambiar archivo';
|
||||
} else if (_domCurrentLocalFile && !_domRemoveOrder) {
|
||||
// Archivo ya guardado en servidor
|
||||
const isImg = /\.(jpe?g|png|gif|webp)$/i.test(_domCurrentLocalFile);
|
||||
if (isImg) {
|
||||
img.src = 'uploads/media/' + encodeURIComponent(_domCurrentLocalFile);
|
||||
img.style.display = '';
|
||||
icon.classList.add('d-none');
|
||||
} else {
|
||||
img.style.display = 'none';
|
||||
icon.classList.remove('d-none');
|
||||
}
|
||||
fname.textContent = _domCurrentLocalFile;
|
||||
fsize.textContent = 'Archivo guardado';
|
||||
preview.classList.remove('d-none');
|
||||
empty.classList.add('d-none');
|
||||
btnQuitar.classList.remove('d-none');
|
||||
btnLbl.textContent = 'Cambiar archivo';
|
||||
} else {
|
||||
preview.classList.add('d-none');
|
||||
empty.classList.remove('d-none');
|
||||
btnQuitar.classList.add('d-none');
|
||||
btnLbl.textContent = 'Adjuntar orden';
|
||||
_domNewFiles.push(file);
|
||||
}
|
||||
input.value = '';
|
||||
_domRenderOrdenes();
|
||||
}
|
||||
|
||||
function domSeleccionarOrdenFile(input) {
|
||||
const file = input.files[0];
|
||||
if (!file) return;
|
||||
if (file.size > 10 * 1024 * 1024) {
|
||||
alert('El archivo supera los 10 MB permitidos');
|
||||
input.value = '';
|
||||
return;
|
||||
}
|
||||
_domOrderFile = file;
|
||||
_domRemoveOrder = false;
|
||||
_domRenderOrdenPreview();
|
||||
function domQuitarExistente(i) {
|
||||
const removed = _domCurrentFiles.splice(i, 1)[0];
|
||||
if (removed?.orden_id) _domRemovedOrdenIds.push(removed.orden_id);
|
||||
_domRenderOrdenes();
|
||||
}
|
||||
|
||||
function domQuitarOrden() {
|
||||
_domOrderFile = null;
|
||||
_domRemoveOrder = true;
|
||||
document.getElementById('dom-orden-id-form').value = '';
|
||||
document.getElementById('dom-orden-input').value = '';
|
||||
_domRenderOrdenPreview();
|
||||
function domQuitarNuevo(i) {
|
||||
_domNewFiles.splice(i, 1);
|
||||
_domRenderOrdenes();
|
||||
}
|
||||
const COLOR_DOM = { programado:'secondary', confirmado:'info', en_camino:'primary', en_domicilio:'warning', completado:'success', cancelado:'danger', reprogramado:'dark' };
|
||||
const PUEDE_ESCRIBIR = <?= $puedeEscribir ? 'true' : 'false' ?>;
|
||||
@@ -1255,11 +1234,15 @@ async function editarDomicilio(id) {
|
||||
document.getElementById('dom-copago-lab').value = dom.copago_laboratorio || '';
|
||||
document.getElementById('dom-notas').value = dom.notas_admin || '';
|
||||
|
||||
// Orden médica adjunta
|
||||
_domResetOrden();
|
||||
document.getElementById('dom-orden-id-form').value = dom.orden_id || '';
|
||||
_domCurrentLocalFile = dom.orden_local_file || null;
|
||||
_domRenderOrdenPreview();
|
||||
// Órdenes médicas adjuntas (multi-archivo)
|
||||
_domResetOrdenes();
|
||||
let archivosExistentes = dom.archivos_ordenes || [];
|
||||
if (!archivosExistentes.length && dom.orden_local_file) {
|
||||
// Retrocompat: domicilio viejo con solo orden_id FK
|
||||
archivosExistentes = [{ orden_id: dom.orden_id || null, local_file: dom.orden_local_file }];
|
||||
}
|
||||
_domCurrentFiles = archivosExistentes;
|
||||
_domRenderOrdenes();
|
||||
|
||||
modal.show();
|
||||
}
|
||||
@@ -1277,57 +1260,52 @@ async function guardarDomicilio() {
|
||||
|
||||
const datos = Object.fromEntries(new FormData(form).entries());
|
||||
if (!datos.id) delete datos.id;
|
||||
// Tomar orden_id del campo oculto real (no el parámetro URL)
|
||||
datos.orden_id = document.getElementById('dom-orden-id-form').value || null;
|
||||
delete datos.orden_id; // archivos se vinculan por domicilio_id; no pasamos FK
|
||||
delete datos.orden_id_url;
|
||||
|
||||
// ── Gestión del archivo de orden médica ──────────────────────────────
|
||||
if (_domRemoveOrder) {
|
||||
datos.orden_id = null;
|
||||
} else if (_domOrderFile) {
|
||||
// Subir el archivo primero
|
||||
const fd = new FormData();
|
||||
fd.append('file', _domOrderFile);
|
||||
const upR = await fetch('api/lab/upload_orden.php', { method:'POST', body: fd });
|
||||
const upD = await upR.json();
|
||||
if (!upD.success) {
|
||||
mostrarToast('Error subiendo el archivo: ' + (upD.error || 'desconocido'), 'danger');
|
||||
return;
|
||||
}
|
||||
// Actualizar o crear la orden médica
|
||||
const orId = document.getElementById('dom-orden-id-form').value;
|
||||
const orPayload = orId
|
||||
? { id: parseInt(orId), local_file: upD.local_file }
|
||||
: { paciente_id: parseInt(pacienteId), local_file: upD.local_file };
|
||||
const orR = await fetch('api/lab/save_orden.php', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify(orPayload),
|
||||
});
|
||||
const orD = await orR.json();
|
||||
if (!orD.success) {
|
||||
mostrarToast('Error guardando la orden: ' + (orD.error || 'desconocido'), 'danger');
|
||||
return;
|
||||
}
|
||||
datos.orden_id = orD.id ?? orD.data?.id ?? (parseInt(orId) || null);
|
||||
// Desvincular archivos que el usuario eliminó
|
||||
for (const ordenId of _domRemovedOrdenIds) {
|
||||
try {
|
||||
await fetch('api/lab/save_orden.php', {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ id: ordenId, domicilio_id: null }),
|
||||
});
|
||||
} catch (_e) { /* silencioso */ }
|
||||
}
|
||||
// ── Guardar domicilio ────────────────────────────────────────────────
|
||||
if (!datos.orden_id) delete datos.orden_id;
|
||||
_domRemovedOrdenIds = [];
|
||||
|
||||
// Guardar domicilio
|
||||
const r = await fetch('api/lab/save_domicilio.php', {
|
||||
method:'POST', headers:{'Content-Type':'application/json'},
|
||||
body: JSON.stringify(datos),
|
||||
});
|
||||
const d = await r.json();
|
||||
if (d.success) {
|
||||
modal.hide();
|
||||
mostrarToast(d.message, 'success');
|
||||
const editadoId = datos.id ? parseInt(datos.id) : (d.id || null);
|
||||
_domResetOrden();
|
||||
await cargarLista(paginaActual);
|
||||
if (editadoId) verDomicilio(editadoId);
|
||||
} else {
|
||||
if (!d.success) {
|
||||
mostrarToast(d.error||'Error', 'danger');
|
||||
return;
|
||||
}
|
||||
|
||||
const domicilioId = datos.id ? parseInt(datos.id) : (d.id || null);
|
||||
|
||||
// Subir nuevos archivos vinculados al domicilio
|
||||
if (_domNewFiles.length && domicilioId) {
|
||||
const fd = new FormData();
|
||||
for (const f of _domNewFiles) fd.append('files[]', f);
|
||||
fd.append('domicilio_id', domicilioId);
|
||||
fd.append('paciente_id', pacienteId);
|
||||
try {
|
||||
const upR = await fetch('api/lab/upload_orden.php', { method: 'POST', body: fd });
|
||||
const upD = await upR.json();
|
||||
if (!upD.success) mostrarToast('No se pudieron subir algunos archivos: ' + (upD.error || ''), 'warning');
|
||||
} catch (_e) { mostrarToast('Error al subir archivos', 'warning'); }
|
||||
}
|
||||
_domNewFiles = [];
|
||||
|
||||
modal.hide();
|
||||
mostrarToast(d.message, 'success');
|
||||
_domResetOrdenes();
|
||||
await cargarLista(paginaActual);
|
||||
if (domicilioId) verDomicilio(domicilioId);
|
||||
}
|
||||
|
||||
// Autocompletado de paciente
|
||||
|
||||
Reference in New Issue
Block a user