diff --git a/backend/src/ANEXO 002 JUAN DAVID GUERRERO DE LA PAVA.pdf b/backend/src/ANEXO 002 JUAN DAVID GUERRERO DE LA PAVA.pdf new file mode 100644 index 0000000..8d302e4 Binary files /dev/null and b/backend/src/ANEXO 002 JUAN DAVID GUERRERO DE LA PAVA.pdf differ diff --git a/backend/src/extraer_autorizacion_pdf.py b/backend/src/extraer_autorizacion_pdf.py index 3525ea7..bf7f86f 100644 --- a/backend/src/extraer_autorizacion_pdf.py +++ b/backend/src/extraer_autorizacion_pdf.py @@ -13,6 +13,20 @@ def normalize(text): return text.upper() +def extract_digits_from_text(text, min_len=6, max_len=20): + if not text: + return None + match = re.search(rf"\b\d{{{min_len},{max_len}}}\b", text) + if match: + return match.group(0) + groups = re.findall(rf"(?:\d[\s\-]*){{{min_len},{max_len}}}", text) + for group in groups: + digits = re.sub(r"\D", "", group) + if min_len <= len(digits) <= max_len: + return digits + return None + + def extract_text_pdfplumber(path): try: import pdfplumber # type: ignore @@ -133,6 +147,13 @@ def extract_name(lines, norm_lines): break if idx == -1: + for line in lines: + if normalize(line).startswith("PACIENTE:"): + parts = line.split(":", 1) + if len(parts) == 2: + candidate = parts[1].split("-")[0].strip() + if candidate: + return candidate return None skip_tokens = [ @@ -154,67 +175,207 @@ def extract_name(lines, norm_lines): continue if len(lines[j].split()) >= 2: return lines[j] + return None def extract_document(lines, norm_lines): idx = -1 for i, line in enumerate(norm_lines): - if "TIPO DOCUMENTO" in line or "TIPO DE DOCUMENTO" in line: + if ( + "TIPO DOCUMENTO" in line + or "TIPO DE DOCUMENTO" in line + or "NUMERO DOCUMENTO" in line + or "DOCUMENTO DE IDENTIFICACION" in line + ): + idx = i + break + + if idx != -1: + for j in range(idx, min(idx + 8, len(lines))): + digits = extract_digits_from_text(lines[j]) + if digits: + return digits + + for i, line in enumerate(norm_lines): + if ( + "CEDULA" in line + or "DOCUMENTO" in line + or "NUIP" in line + or "PASAPORTE" in line + ): + digits = extract_digits_from_text(lines[i]) + if digits: + return digits + + return None + + +def extract_cups_code(text): + if not text: + return None + match = re.search(r"\b[A-Z0-9]{4,10}\b", text, re.IGNORECASE) + if match: + return match.group(0) + digits = extract_digits_from_text(text, min_len=4, max_len=10) + if digits: + return digits + return None + + +def extract_cups(lines, norm_lines): + for i, line in enumerate(norm_lines): + if ( + "CUPS" in line + or re.search(r"C\s*[\.\-]?\s*U\s*[\.\-]?\s*P\s*[\.\-]?\s*S", line, re.IGNORECASE) + or re.search(r"\bCUP\b", line, re.IGNORECASE) + ): + for j in range(i, min(i + 8, len(lines))): + code = extract_cups_code(lines[j]) + if not code: + continue + desc = "" + raw = lines[j] + if code in raw: + desc = raw.split(code, 1)[-1].strip(" -:") + if not desc and j + 1 < len(lines): + desc = lines[j + 1].strip() + return code, desc or None + + return None, None + + +def parse_cie10_from_line(line): + if not line: + return None, None + match = re.search(r"\(([A-Z][0-9]{2,4}[A-Z0-9]?)\)", line, re.IGNORECASE) + if match: + code = match.group(1).upper() + desc = line[: match.start()].strip(" -:") + desc = re.sub(r"(?i)diagnostico principal", "", desc).strip() + desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip() + return code, desc.title() if desc else None + match = re.search(r"\b([A-Z])(?:\s*\d){2,4}[A-Z0-9]?\b", line, re.IGNORECASE) + if match: + code = re.sub(r"\s+", "", match.group(0)).upper() + desc = line[match.end():].strip(" -:") + if not desc: + desc = line[: match.start()].strip(" -:") + desc = re.sub(r"(?i)diagnostico principal", "", desc).strip() + desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip() + return code, desc.title() if desc else None + match = re.search(r"\b([A-Z][0-9]{2,4}[A-Z0-9]?)\b", line, re.IGNORECASE) + if match: + code = match.group(1).upper() + desc = line[match.end():].strip(" -:") + return code, desc.title() if desc else None + return None, None + + +def extract_cups_hint(lines, norm_lines): + keys = [ + "EXAMENES Y PROCEDIMIENTOS ORDENADOS", + "EXAMENES Y PROCEDIMIENTOS", + "PROCEDIMIENTOS ORDENADOS", + "PROCEDIMIENTOS", + ] + idx = -1 + for key in keys: + for i, line in enumerate(norm_lines): + if key in line: + idx = i + break + if idx != -1: + break + + if idx == -1: + return None + + for j in range(idx + 1, min(idx + 10, len(lines))): + raw = lines[j].strip(" -*") + nline = norm_lines[j] + if not raw or "ORDEN NRO" in nline or "ORDEN NO" in nline: + continue + if len(raw.split()) < 2: + continue + candidate = raw.split("(")[0].strip(" -") + candidate = re.sub(r"(?i)AUTOMATIZADO", "", candidate).strip(" -") + if candidate: + return candidate + return None + + +def extract_cie10(lines, norm_lines): + for i, nline in enumerate(norm_lines): + if ( + "DIAGNOSTICO PRINCIPAL" in nline + or "IMPRESION DIAGNOSTICA" in nline + or "DIAGNOSTICO" in nline + ): + candidate = lines[i] + if i + 1 < len(lines) and len(lines[i + 1].split()) > 1: + candidate = candidate + " " + lines[i + 1] + code, desc = parse_cie10_from_line(candidate) + if code: + return code, desc or None + for line in lines: + code, desc = parse_cie10_from_line(line) + if code: + return code, desc or None + return None, None + + +def clean_ips_name(value): + if not value: + return "" + text = value.strip() + text = re.split(r"\bCC\b", text, maxsplit=1, flags=re.IGNORECASE)[0] + text = re.split(r"\bNUMERO\b", text, maxsplit=1, flags=re.IGNORECASE)[0] + text = re.split(r"\bNIT\b", text, maxsplit=1, flags=re.IGNORECASE)[0] + text = re.split(r"\bCODIGO\b", text, maxsplit=1, flags=re.IGNORECASE)[0] + return text.strip(" -:") + + +def extract_ips(lines, norm_lines): + nombre = None + nit = None + idx = -1 + for i, line in enumerate(norm_lines): + if "INFORMACION DEL PRESTADOR" in line: idx = i break if idx != -1: for j in range(idx + 1, min(idx + 8, len(lines))): - digits = re.findall(r"\d{6,}", lines[j]) - if digits: - return digits[-1] + if not nit and "NIT" in norm_lines[j]: + nit = extract_digits_from_text(lines[j], min_len=6, max_len=15) + if not nombre: + if "NOMBRE" in norm_lines[j] and ":" in lines[j] and "NIT" not in norm_lines[j]: + candidate = lines[j].split(":", 1)[1].strip() + candidate = clean_ips_name(candidate) + if len(candidate.split()) >= 2: + nombre = candidate + continue + if ( + "NOMBRE" not in norm_lines[j] + and "NIT" not in norm_lines[j] + and "CODIGO" not in norm_lines[j] + ): + candidate = clean_ips_name(lines[j]) + if len(candidate.split()) >= 2: + nombre = candidate + if nombre and nit: + break - for i, line in enumerate(norm_lines): - if "CEDULA" in line or "DOCUMENTO" in line or "NUIP" in line: - digits = re.findall(r"\d{6,}", lines[i]) - if digits: - return digits[-1] + if not nombre: + for line in lines[:10]: + if re.search(r"\b(HOSPITAL|CLINICA|ESE|IPS|CENTRO MEDICO)\b", line, re.IGNORECASE): + candidate = clean_ips_name(line) + if len(candidate.split()) >= 2: + nombre = candidate + break - return None - - -def extract_cups(lines, norm_lines): - idx = -1 - for i, line in enumerate(norm_lines): - if "CODIGO CUPS" in line: - idx = i - break - - if idx == -1: - return None, None - - for j in range(idx + 1, min(idx + 6, len(lines))): - tokens = lines[j].split() - if not tokens: - continue - code = tokens[0] - if not re.match(r"^[A-Z0-9]{4,10}$", code, re.IGNORECASE): - continue - desc = "" - if len(tokens) >= 2 and re.match(r"^\d+[,.]\d+$", tokens[1]): - desc = " ".join(tokens[2:]) - else: - desc = " ".join(tokens[1:]) - return code, desc.strip() or None - - return None, None - - -def extract_cie10(lines, norm_lines): - for i, nline in enumerate(norm_lines): - if "DIAGNOSTICO PRINCIPAL" in nline: - match = re.search(r"DIAGNOSTICO PRINCIPAL\s+([A-Z0-9]{3,6})\s*(.*)", nline) - if match: - code = match.group(1) - desc = match.group(2).strip().title() - return code, desc or None - return None, None + return nombre or None, nit or None def detect_format(norm_text, norm_lines): @@ -234,7 +395,9 @@ def build_response(text, ocr_used, ocr_available, ocr_error): nombre = extract_name(lines, norm_lines) documento = extract_document(lines, norm_lines) cup_codigo, cup_desc = extract_cups(lines, norm_lines) + cups_busqueda = extract_cups_hint(lines, norm_lines) cie_codigo, cie_desc = extract_cie10(lines, norm_lines) + ips_nombre, ips_nit = extract_ips(lines, norm_lines) formato = detect_format(norm_text, norm_lines) warnings = [] @@ -244,6 +407,8 @@ def build_response(text, ocr_used, ocr_available, ocr_error): warnings.append("cups_not_found") if not cie_codigo: warnings.append("cie10_not_found") + if not ips_nombre and not ips_nit: + warnings.append("ips_not_found") return { "ok": True, @@ -256,8 +421,11 @@ def build_response(text, ocr_used, ocr_available, ocr_error): "numero_documento": documento, "cup_codigo": cup_codigo, "cup_descripcion": cup_desc, + "cups_busqueda": cups_busqueda, "cie10_codigo": cie_codigo, "cie10_descripcion": cie_desc, + "ips_nombre": ips_nombre, + "ips_nit": ips_nit, "warnings": warnings, } diff --git a/backend/src/hc juan david guerrero de la pava.pdf b/backend/src/hc juan david guerrero de la pava.pdf new file mode 100644 index 0000000..5e1d876 Binary files /dev/null and b/backend/src/hc juan david guerrero de la pava.pdf differ diff --git a/backend/src/plantilla_autorizacion.js b/backend/src/plantilla_autorizacion.js index f474668..27c983c 100644 --- a/backend/src/plantilla_autorizacion.js +++ b/backend/src/plantilla_autorizacion.js @@ -2697,6 +2697,11 @@ async function crearLibroAutorizacion(a) { sheet.getCell(celda).value = nitIps; }); + const telefonoIps = a.telefono_ips || ''; + ['D14', 'E14', 'F14', 'G14', 'D15', 'E15', 'F15', 'G15', 'D16', 'E16', 'F16', 'G16'].forEach(celda => { + sheet.getCell(celda).value = telefonoIps; + }); + sheet.getCell('M21').value = a.cie10_codigo || ''; sheet.getCell('N21').value = a.cie10_descripcion || ''; @@ -2753,22 +2758,29 @@ async function crearLibroAutorizacion(a) { if (cupDescripcion) cupInfoParts.push(cupDescripcion); if (nivelTexto) cupInfoParts.push(nivelTexto); const cupInfo = cupInfoParts.join(' - '); - const observacionBase = a.observacion || ''; + const limpiarObservacion = (value) => { + const partes = String(value || '') + .split('|') + .map((parte) => parte.trim()) + .filter(Boolean) + .filter((parte) => { + const lower = parte.toLowerCase(); + return ( + !lower.includes('ips sin convenio') && + !lower.includes('cups no cubierto') && + !lower.includes('cargado por') + ); + }); + return partes.join(' | '); + }; + const observacionBase = limpiarObservacion(a.observacion || ''); const solicitanteNombre = String(a.nombre_solicitante || '').trim(); const observacionLower = observacionBase.toLowerCase(); const solicitanteInfo = solicitanteNombre && !observacionLower.includes('solicitante') ? `Solicitante: ${solicitanteNombre}` : ''; - const tipoAutorizacion = (a.tipo_autorizacion || 'consultas_externas').toLowerCase(); - const tipoServicioRaw = String(a.tipo_servicio || '').trim(); - let tipoServicioTexto = ''; - if (tipoServicioRaw) { - tipoServicioTexto = `Tipo servicio: ${tipoServicioRaw}`; - } else if (tipoAutorizacion === 'consultas_externas') { - tipoServicioTexto = 'Tipo servicio: Consulta externa'; - } - const observacion = [cupInfo, tipoServicioTexto, observacionBase, solicitanteInfo] + const observacion = [cupInfo, observacionBase, solicitanteInfo] .filter(Boolean) .join(' | '); ['H31','R31'].forEach(celda => { diff --git a/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js b/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js index eabc31b..9b0190e 100644 --- a/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js +++ b/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js @@ -2373,6 +2373,11 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { sheet.getCell(celda).value = nitIps; }); + const telefonoIps = a.telefono_ips || ''; + ['D14', 'E14', 'F14', 'G14', 'D15', 'E15', 'F15', 'G15', 'D16', 'E16', 'F16', 'G16'].forEach((celda) => { + sheet.getCell(celda).value = telefonoIps; + }); + sheet.getCell('M21').value = a.cie10_codigo || ''; sheet.getCell('N21').value = a.cie10_descripcion || ''; @@ -2423,7 +2428,22 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { if (cupDescripcion) cupInfoParts.push(cupDescripcion); if (nivelTexto) cupInfoParts.push(nivelTexto); const cupInfo = cupInfoParts.join(' - '); - const observacionBase = a.observacion || ''; + const limpiarObservacion = (value) => { + const partes = String(value || '') + .split('|') + .map((parte) => parte.trim()) + .filter(Boolean) + .filter((parte) => { + const lower = parte.toLowerCase(); + return ( + !lower.includes('ips sin convenio') && + !lower.includes('cups no cubierto') && + !lower.includes('cargado por') + ); + }); + return partes.join(' | '); + }; + const observacionBase = limpiarObservacion(a.observacion || ''); const solicitanteNombre = String(a.nombre_solicitante || '').trim(); const observacionLower = observacionBase.toLowerCase(); const solicitanteInfo = diff --git a/backend/src/server.js b/backend/src/server.js index 3114c44..c0745d1 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -152,7 +152,7 @@ const extraerDatosPdfAutorizacion = (filePath) => { pythonPath, [scriptPath, filePath], { cwd: __dirname }, - (error, stdout, stderr) => { + async (error, stdout, stderr) => { if (error) { return reject( new Error(stderr?.trim() || error.message || 'Error ejecutando extractor') @@ -160,6 +160,67 @@ const extraerDatosPdfAutorizacion = (filePath) => { } try { const data = parseJsonOutput(stdout); + const nit = String(data?.ips_nit || '').replace(/\D/g, ''); + const nombre = String(data?.ips_nombre || '').trim(); + let ipsRow = null; + + if (nit) { + const { rows } = await pool.query( + 'SELECT id_ips, nombre_ips, nit FROM ips WHERE nit = $1 LIMIT 1', + [nit] + ); + ipsRow = rows[0] || null; + } + + if (!ipsRow && nombre) { + const nombreClean = nombre.replace(/\s+/g, ' ').trim(); + const nombreLike = `%${nombreClean}%`; + const { rows } = await pool.query( + ` + SELECT id_ips, nombre_ips, nit + FROM ips + WHERE UPPER(nombre_ips) = UPPER($1) + OR nombre_ips ILIKE $2 + LIMIT 1 + `, + [nombreClean, nombreLike] + ); + ipsRow = rows[0] || null; + } + + if (ipsRow) { + data.id_ips = ipsRow.id_ips; + data.ips_nombre = ipsRow.nombre_ips; + data.ips_nit = ipsRow.nit; + } + + const cupCodigoActual = String(data?.cup_codigo || '').trim(); + const cupBusqueda = String(data?.cups_busqueda || '').trim(); + if (!cupCodigoActual && cupBusqueda.length >= 4) { + const term = cupBusqueda.replace(/\s+/g, ' ').trim(); + const { rows } = await pool.query( + ` + SELECT codigo, descripcion + FROM cups_cubiertos + WHERE descripcion ILIKE $1 + ORDER BY + CASE WHEN UPPER(descripcion) = UPPER($2) THEN 0 ELSE 1 END, + LENGTH(descripcion) ASC + LIMIT 2 + `, + [`%${term}%`, term] + ); + if (rows.length >= 1) { + data.cup_codigo = rows[0].codigo; + data.cup_descripcion = rows[0].descripcion; + if (Array.isArray(data.warnings)) { + data.warnings = data.warnings.filter((w) => w !== 'cups_not_found'); + } + if (rows.length > 1 && Array.isArray(data.warnings)) { + data.warnings.push('cups_multiple_matches'); + } + } + } resolve(data); } catch (parseError) { reject(parseError); @@ -1067,6 +1128,12 @@ app.post( upload.single('archivo'), async (req, res) => { try { + if (req.usuario.nombre_rol !== 'administrador') { + return res.status(403).json({ + error: 'Solo administradores pueden autorrellenar autorizaciones', + }); + } + if (!req.file) { return res.status(400).json({ error: 'No se recibio archivo PDF' }); } @@ -1108,6 +1175,12 @@ app.post( return res.status(400).json({ error: 'numero_autorizacion es obligatorio' }); } + if (req.usuario.nombre_rol !== 'administrador') { + return res.status(403).json({ + error: 'Solo administradores pueden subir archivos hospitalarios', + }); + } + const historialFile = req.files?.historial_clinico?.[0] || null; const anexoFile = req.files?.anexo?.[0] || null; @@ -2070,6 +2143,9 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { continue; } + const servicioExcel = getValue(row, 'SERVICIO'); + const servicioInfo = parseServicio(servicioExcel); + const ambitoRaw = getValueMulti(row, [ 'AMBITO', 'AMBITOATENCION', @@ -2078,7 +2154,10 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { 'TIPOATENCION', 'TIPODEORDEN', ]); - const ambitoAtencion = parseAmbito(ambitoRaw); + let ambitoAtencion = parseAmbito(ambitoRaw); + if (servicioInfo.tipo_servicio === 'hospitalarios') { + ambitoAtencion = 'extramural'; + } if (!ambitoAtencion) { resumen.omitidas += 1; resumen.sin_ambito += 1; @@ -2270,8 +2349,19 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { resumen.ips_sin_convenio += 1; } - const servicioExcel = getValue(row, 'SERVICIO'); - const servicioInfo = parseServicio(servicioExcel); + if ( + servicioInfo.tipo_servicio === 'hospitalarios' && + usuario?.nombre_rol !== 'administrador' + ) { + resumen.omitidas += 1; + if (resumen.errores.length < 50) { + resumen.errores.push({ + fila: i, + error: 'Hospitalarios solo permitido para administradores', + }); + } + continue; + } const dupRes = await client.query( ` @@ -2313,15 +2403,6 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { if (obs1) observaciones.push(obs1); if (obs2) observaciones.push(obs2); if (solicitante) observaciones.push(`Solicitante: ${solicitante}`); - if (!ipsInfo.tiene_convenio) { - observaciones.push(`IPS sin convenio${hospital ? `: ${hospital}` : ''}`); - } - if (!cupCubierto) { - observaciones.push(`CUPS no cubierto: ${cupCodigo}`); - } - if (usuario?.nombre_completo) { - observaciones.push(`Cargado por: ${usuario.nombre_completo}`); - } const observacionFinal = observaciones.filter(Boolean).join(' | ') || null; @@ -2431,6 +2512,7 @@ async function generarPdfAutorizacionYObtenerPath( ips.nombre_ips, ips.nit, ips.direccion, + ips.telefono AS telefono_ips, ips.municipio, ips.departamento, aut.nombre AS nombre_autorizante, @@ -2439,7 +2521,7 @@ async function generarPdfAutorizacionYObtenerPath( aut.telefono AS tel_autorizante, e.nombre_establecimiento, e.epc_departamento, - cc.descripcion AS cup_descripcion, + COALESCE(cc.descripcion, cr.descripcion) AS cup_descripcion, cc.nivel AS cup_nivel, cc.especialidad AS cup_especialidad FROM autorizacion a @@ -2457,6 +2539,8 @@ async function generarPdfAutorizacionYObtenerPath( ON i.codigo_establecimiento = e.codigo_establecimiento LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo + LEFT JOIN cups_referencia cr + ON a.cup_codigo = cr.codigo WHERE a.numero_autorizacion = $1 LIMIT 1; `; @@ -2481,6 +2565,7 @@ async function generarPdfAutorizacionYObtenerPath( ips.nombre_ips, ips.nit, ips.direccion, + ips.telefono AS telefono_ips, ips.municipio, ips.departamento, aut.nombre AS nombre_autorizante, @@ -2489,7 +2574,7 @@ async function generarPdfAutorizacionYObtenerPath( aut.telefono AS tel_autorizante, e.nombre_establecimiento, e.epc_departamento, - cc.descripcion AS cup_descripcion, + COALESCE(cc.descripcion, cr.descripcion) AS cup_descripcion, cc.nivel AS cup_nivel, cc.especialidad AS cup_especialidad FROM autorizacion a @@ -2509,6 +2594,8 @@ async function generarPdfAutorizacionYObtenerPath( ON i.codigo_establecimiento = e.codigo_establecimiento LEFT JOIN cups_cubiertos cc ON av.cup_codigo = cc.codigo + LEFT JOIN cups_referencia cr + ON av.cup_codigo = cr.codigo WHERE a.numero_autorizacion = $1 AND av.version = $2 LIMIT 1; @@ -2685,7 +2772,7 @@ async function generarZipAutorizacionesPorFecha( aut.telefono AS tel_autorizante, e.nombre_establecimiento, e.epc_departamento, - cc.descripcion AS cup_descripcion, + COALESCE(cc.descripcion, cr.descripcion) AS cup_descripcion, cc.nivel AS cup_nivel, cc.especialidad AS cup_especialidad FROM autorizacion a @@ -2700,6 +2787,8 @@ async function generarZipAutorizacionesPorFecha( ON i.codigo_establecimiento = e.codigo_establecimiento LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo + LEFT JOIN cups_referencia cr + ON a.cup_codigo = cr.codigo WHERE a.numero_autorizacion = $1 LIMIT 1; `; @@ -3384,21 +3473,6 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn return res.status(400).json({ error: 'cie10_descripcion es obligatorio' }); } - const ambitoAtencion = String(ambito_atencion || '').trim().toLowerCase(); - const ambitosPermitidos = ['intramural', 'extramural']; - if (!ambitoAtencion) { - return res.status(400).json({ error: 'ambito_atencion es obligatorio' }); - } - if (!ambitosPermitidos.includes(ambitoAtencion)) { - return res.status(400).json({ error: 'ambito_atencion invalido' }); - } - - const numeroOrdenInput = String(numero_orden || '').trim(); - const numeroOrdenFinal = - ambitoAtencion === 'intramural' && numeroOrdenInput - ? numeroOrdenInput - : null; - const estadoEntregaInput = estado_entrega === undefined ? '' : String(estado_entrega); const estadoEntregaParsed = estadoEntregaInput @@ -3434,6 +3508,30 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn // ... aquí va tu lógica de permisos por rol / sede ... + if (tipoServicio === 'hospitalarios' && req.usuario.nombre_rol !== 'administrador') { + return res.status(403).json({ + error: 'Solo administradores pueden generar autorizaciones hospitalarias', + }); + } + + let ambitoAtencion = String(ambito_atencion || '').trim().toLowerCase(); + const ambitosPermitidos = ['intramural', 'extramural']; + if (tipoServicio === 'hospitalarios') { + ambitoAtencion = 'extramural'; + } + if (!ambitoAtencion) { + return res.status(400).json({ error: 'ambito_atencion es obligatorio' }); + } + if (!ambitosPermitidos.includes(ambitoAtencion)) { + return res.status(400).json({ error: 'ambito_atencion invalido' }); + } + + const numeroOrdenInput = String(numero_orden || '').trim(); + let numeroOrdenFinal = null; + if (ambitoAtencion === 'intramural' && numeroOrdenInput) { + numeroOrdenFinal = numeroOrdenInput; + } + const solicitanteId = req.usuario?.id_usuario ? Number(req.usuario.id_usuario) : null; const client = await pool.connect(); @@ -3621,10 +3719,6 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar const cie10CodigoInput = String(cie10_codigo || '').trim().toUpperCase(); const cie10DescripcionInput = String(cie10_descripcion || '').trim(); const ambitoInput = String(ambito_atencion || '').trim().toLowerCase(); - const ambitosPermitidos = ['intramural', 'extramural']; - if (ambitoInput && !ambitosPermitidos.includes(ambitoInput)) { - return res.status(400).json({ error: 'ambito_atencion invalido' }); - } const tipoAutorizacion = String(tipo_autorizacion || 'consultas_externas') .trim() @@ -3649,6 +3743,12 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar tipoServicio = servicio; } + if (tipoServicio === 'hospitalarios' && req.usuario.nombre_rol !== 'administrador') { + return res.status(403).json({ + error: 'Solo administradores pueden actualizar autorizaciones hospitalarias', + }); + } + const client = await pool.connect(); try { @@ -3668,7 +3768,11 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar const versionActual = Number(actual.version) || 1; const cie10Codigo = cie10CodigoInput || actual.cie10_codigo || ''; const cie10Descripcion = cie10DescripcionInput || actual.cie10_descripcion || ''; - const ambitoAtencion = ambitoInput || actual.ambito_atencion || ''; + const ambitosPermitidos = ['intramural', 'extramural']; + let ambitoAtencion = ambitoInput || actual.ambito_atencion || ''; + if (tipoServicio === 'hospitalarios') { + ambitoAtencion = 'extramural'; + } if (!cie10Codigo) { await client.query('ROLLBACK'); diff --git a/backend/src/uploads/autorizaciones/UTUSCPGB51/ANEXO_1767385620084_70929f61.pdf b/backend/src/uploads/autorizaciones/UTUSCPGB51/ANEXO_1767385620084_70929f61.pdf new file mode 100644 index 0000000..8d302e4 Binary files /dev/null and b/backend/src/uploads/autorizaciones/UTUSCPGB51/ANEXO_1767385620084_70929f61.pdf differ diff --git a/backend/src/uploads/autorizaciones/UTUSCPGB51/HC_1767385620067_0d3c6379.pdf b/backend/src/uploads/autorizaciones/UTUSCPGB51/HC_1767385620067_0d3c6379.pdf new file mode 100644 index 0000000..5e1d876 Binary files /dev/null and b/backend/src/uploads/autorizaciones/UTUSCPGB51/HC_1767385620067_0d3c6379.pdf differ diff --git a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.html b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.html index 301e624..f027d48 100644 --- a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.html +++ b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.html @@ -172,13 +172,13 @@ - +
@@ -196,7 +196,7 @@
@@ -214,7 +214,7 @@
@@ -243,6 +243,7 @@ id="ambitoAtencion" [(ngModel)]="formAutorizacion.ambito_atencion" (change)="onAmbitoChange()" + [disabled]="formAutorizacion.tipo_servicio === 'hospitalarios'" > diff --git a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts index 724331e..8c6421d 100644 --- a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts +++ b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts @@ -345,12 +345,30 @@ export class AutorizacionesComponent { const servicio = String(this.formAutorizacion.tipo_servicio || '') .trim() .toLowerCase(); + if (servicio === 'hospitalarios' && !this.isAdministrador()) { + this.formAutorizacion.tipo_servicio = ''; + this.limpiarArchivosHospitalarios(); + this.errorAutorizacion = 'Solo administradores pueden seleccionar hospitalarios.'; + return; + } + if (servicio === 'hospitalarios') { + this.formAutorizacion.ambito_atencion = 'extramural'; + this.formAutorizacion.numero_orden = ''; + } if (servicio !== 'hospitalarios') { this.limpiarArchivosHospitalarios(); } } onAmbitoChange(): void { + const servicio = String(this.formAutorizacion.tipo_servicio || '') + .trim() + .toLowerCase(); + if (servicio === 'hospitalarios') { + this.formAutorizacion.ambito_atencion = 'extramural'; + this.formAutorizacion.numero_orden = ''; + return; + } const ambito = String(this.formAutorizacion.ambito_atencion || '') .trim() .toLowerCase(); @@ -398,6 +416,18 @@ export class AutorizacionesComponent { if (resp?.cie10_descripcion) { this.formAutorizacion.cie10_descripcion = resp.cie10_descripcion; } + if (resp?.id_ips) { + this.formAutorizacion.id_ips = String(resp.id_ips); + const existe = this.ipsDisponibles.some( + (ips) => String(ips.id_ips) === String(resp.id_ips) + ); + if (!existe && this.pacienteSeleccionado) { + this.verMasIps = true; + this.cargarIps(this.pacienteSeleccionado.interno, true); + } else { + this.onIpsChange(); + } + } const infoParts = []; if (resp?.nombre_paciente) { @@ -406,6 +436,9 @@ export class AutorizacionesComponent { if (resp?.numero_documento) { infoParts.push(`Documento: ${resp.numero_documento}`); } + if (resp?.ips_nombre) { + infoParts.push(`IPS: ${resp.ips_nombre}`); + } if (resp?.formato) { infoParts.push(`Formato: ${this.getFormatoPdfLabel(resp.formato)}`); } @@ -657,6 +690,7 @@ export class AutorizacionesComponent { this.cupSeleccionado = null; this.limpiarArchivosHospitalarios(); + this.onTipoServicioChange(); this.onTipoAutorizacionChange(); this.onIpsChange(); if (this.pacienteSeleccionado) { @@ -692,6 +726,14 @@ export class AutorizacionesComponent { this.errorAutorizacion = null; this.mensajeAutorizacion = null; + const servicio = String(this.formAutorizacion.tipo_servicio || '') + .trim() + .toLowerCase(); + if (servicio === 'hospitalarios' && !this.isAdministrador()) { + this.errorAutorizacion = 'Solo administradores pueden generar hospitalarios.'; + return; + } + if (!this.formAutorizacion.id_ips) { this.errorAutorizacion = 'Debe seleccionar una IPS.'; return;