This commit is contained in:
Jhonathan Guevara 2026-01-02 16:00:09 -05:00
parent e7d3ab34ed
commit a4cf0f84c3
Signed by: jhonathan_guevara
GPG Key ID: 619239F12DCBE55B
10 changed files with 446 additions and 99 deletions

View File

@ -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]
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]
return None
def extract_cups(lines, norm_lines):
idx = -1
for i, line in enumerate(norm_lines):
if "CODIGO CUPS" in line:
idx = i
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
if idx == -1:
return None, None
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
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,
}

Binary file not shown.

View File

@ -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 => {

View File

@ -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 =

View File

@ -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');

View File

@ -172,13 +172,13 @@
<option value="">-- Seleccione --</option>
<option value="brigadas">Brigadas</option>
<option value="ambulancias">Ambulancias</option>
<option value="hospitalarios">Hospitalarios</option>
<option value="hospitalarios" *ngIf="isAdministrador()">Hospitalarios</option>
</select>
</div>
<div
class="form-row"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios'"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios' && isAdministrador()"
>
<label for="historialClinico">Historial clinico (HC):</label>
<div class="file-field">
@ -196,7 +196,7 @@
<div
class="form-row"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios'"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios' && isAdministrador()"
>
<label for="anexoHospitalario">Anexo:</label>
<div class="file-field">
@ -214,7 +214,7 @@
<div
class="form-row"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios'"
*ngIf="formAutorizacion.tipo_servicio === 'hospitalarios' && isAdministrador()"
>
<label></label>
<div class="file-actions">
@ -243,6 +243,7 @@
id="ambitoAtencion"
[(ngModel)]="formAutorizacion.ambito_atencion"
(change)="onAmbitoChange()"
[disabled]="formAutorizacion.tipo_servicio === 'hospitalarios'"
>
<option value="">-- Seleccione --</option>
<option value="intramural">Intramural</option>

View File

@ -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;