diff --git a/README.md b/README.md index 59af1e8..7f07d6e 100644 --- a/README.md +++ b/README.md @@ -299,3 +299,45 @@ pip3 install --break-system-packages pdfplumber pypdf reportlab pillow # verificacion python3 -c "import pandas, openpyxl, dotenv; print('OK', pandas.__version__, openpyxl.__version__, dotenv.__version__)" ``` + +#### 7.7) OCR (PDF real + escaneado) con Tesseract (Alpine) +Si necesitas leer PDFs escaneados para el autorrelleno, usa este flujo. +```bash +# Uso: +# sh -c '...este bloque...' archivo.pdf +# Si no pasas argumento, usa "archivo.pdf" + +# 1) Asegura repo community (donde esta ocrmypdf/tesseract-data) +ALPINE_VER="$(cut -d. -f1,2 /etc/alpine-release)" +sed -i 's/^#\(.*\/community\)/\1/' /etc/apk/repositories +grep -q "/v${ALPINE_VER}/community" /etc/apk/repositories || \ + echo "https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VER}/community" >> /etc/apk/repositories + +# 2) Instala herramientas: pdftotext (poppler), ocrmypdf, tesseract + idiomas +apk update +apk add --no-cache \ + poppler-utils \ + ocrmypdf \ + tesseract-ocr-data-spa \ + tesseract-ocr-data-eng + +# 3) Procesa el PDF (real/escaneado/mixto) +IN="${1:-archivo.pdf}" +BASE="${IN%.pdf}" +TXT="${BASE}.txt" +OCRPDF="${BASE}.ocr.pdf" + +# Intenta extraer texto directo +pdftotext -layout "$IN" - > "$TXT" 2>/dev/null || true + +# Si el texto es muy corto, probablemente es escaneado (o casi todo imagen) -> OCR +LEN="$(tr -d '[:space:]' < "$TXT" | wc -c | tr -d ' ')" +if [ "${LEN:-0}" -lt 80 ]; then + ocrmypdf -l spa+eng --skip-text --rotate-pages --deskew --optimize 3 "$IN" "$OCRPDF" + pdftotext -layout "$OCRPDF" "$TXT" + echo "OK (OCR aplicado cuando hacia falta): $OCRPDF | Texto: $TXT" +else + echo "OK (PDF con texto): Texto: $TXT" +fi +``` + diff --git a/backend/src/ANEXO 1 LUIS CARLOS VARGAS MAYORGA CC 1022361409.pdf b/backend/src/ANEXO 1 LUIS CARLOS VARGAS MAYORGA CC 1022361409.pdf new file mode 100644 index 0000000..8637a48 Binary files /dev/null and b/backend/src/ANEXO 1 LUIS CARLOS VARGAS MAYORGA CC 1022361409.pdf differ diff --git a/backend/src/ANEXO 3 SOL MARIA BAUTISTA DIAZ.pdf b/backend/src/ANEXO 3 SOL MARIA BAUTISTA DIAZ.pdf new file mode 100644 index 0000000..b4c4ef2 Binary files /dev/null and b/backend/src/ANEXO 3 SOL MARIA BAUTISTA DIAZ.pdf differ diff --git a/backend/src/ANEXO CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf b/backend/src/ANEXO CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf new file mode 100644 index 0000000..c602a30 Binary files /dev/null and b/backend/src/ANEXO CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf differ diff --git a/backend/src/CC 1015401397 -- ANDRES MAURICIO CONTRERAS JIMENEZ.pdf b/backend/src/CC 1015401397 -- ANDRES MAURICIO CONTRERAS JIMENEZ.pdf new file mode 100644 index 0000000..96649eb Binary files /dev/null and b/backend/src/CC 1015401397 -- ANDRES MAURICIO CONTRERAS JIMENEZ.pdf differ diff --git a/backend/src/CC 1122730848 - LIBER IBAÑEZ BOLAÑOS.pdf b/backend/src/CC 1122730848 - LIBER IBAÑEZ BOLAÑOS.pdf new file mode 100644 index 0000000..a01848f Binary files /dev/null and b/backend/src/CC 1122730848 - LIBER IBAÑEZ BOLAÑOS.pdf differ diff --git a/backend/src/CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf b/backend/src/CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf new file mode 100644 index 0000000..8aa955b Binary files /dev/null and b/backend/src/CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf differ diff --git a/backend/src/CONTRERAS JIMENEZ ANDRES MAURICIO.pdf b/backend/src/CONTRERAS JIMENEZ ANDRES MAURICIO.pdf new file mode 100644 index 0000000..189810e Binary files /dev/null and b/backend/src/CONTRERAS JIMENEZ ANDRES MAURICIO.pdf differ diff --git a/backend/src/GUEVARA CAMARGO CESAR AUGUSTO.pdf b/backend/src/GUEVARA CAMARGO CESAR AUGUSTO.pdf new file mode 100644 index 0000000..4e29674 Binary files /dev/null and b/backend/src/GUEVARA CAMARGO CESAR AUGUSTO.pdf differ diff --git a/backend/src/HC CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf b/backend/src/HC CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf new file mode 100644 index 0000000..142c61f Binary files /dev/null and b/backend/src/HC CC 79607978 -- CESAR AUGUSTO GUEVARA CAMARGO.pdf differ diff --git a/backend/src/IBAÑEZ BOLAÑOS LIBER NO TIENE.pdf b/backend/src/IBAÑEZ BOLAÑOS LIBER NO TIENE.pdf new file mode 100644 index 0000000..473d194 Binary files /dev/null and b/backend/src/IBAÑEZ BOLAÑOS LIBER NO TIENE.pdf differ diff --git a/backend/src/autorizacion_masivo.xlsx b/backend/src/autorizacion_masivo.xlsx new file mode 100644 index 0000000..0bfc547 Binary files /dev/null and b/backend/src/autorizacion_masivo.xlsx differ diff --git a/backend/src/extraer_autorizacion_pdf.py b/backend/src/extraer_autorizacion_pdf.py new file mode 100644 index 0000000..3525ea7 --- /dev/null +++ b/backend/src/extraer_autorizacion_pdf.py @@ -0,0 +1,292 @@ +import json +import os +import re +import sys +import unicodedata + + +def normalize(text): + text = text or "" + text = unicodedata.normalize("NFD", text) + text = "".join(ch for ch in text if not unicodedata.combining(ch)) + text = re.sub(r"\s+", " ", text).strip() + return text.upper() + + +def extract_text_pdfplumber(path): + try: + import pdfplumber # type: ignore + except Exception: + return "" + + parts = [] + with pdfplumber.open(path) as pdf: + for page in pdf.pages: + parts.append(page.extract_text() or "") + return "\n".join(parts) + + +def extract_text_fitz(path): + try: + import fitz # type: ignore + except Exception: + return "" + + parts = [] + doc = fitz.open(path) + try: + for page in doc: + parts.append(page.get_text() or "") + finally: + doc.close() + return "\n".join(parts) + + +def configure_tesseract(): + try: + import pytesseract # type: ignore + except Exception: + return None + + candidates = [] + env_path = os.environ.get("TESSERACT_PATH") + if env_path: + candidates.append(env_path) + + candidates.extend( + [ + r"C:\Program Files\Tesseract-OCR\tesseract.exe", + r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe", + ] + ) + + for path in candidates: + if path and os.path.isfile(path): + pytesseract.pytesseract.tesseract_cmd = path + return path + + return None + + +def is_tesseract_available(): + try: + import pytesseract # type: ignore + except Exception: + return False + + configure_tesseract() + try: + _ = pytesseract.get_tesseract_version() + return True + except Exception: + return False + + +def extract_text_ocr(path, max_pages=2): + try: + import fitz # type: ignore + import pytesseract # type: ignore + from PIL import Image # type: ignore + except Exception: + return "", "ocr_modules_missing" + + configure_tesseract() + try: + _ = pytesseract.get_tesseract_version() + except Exception: + return "", "tesseract_not_found" + + text_parts = [] + ocr_error = "" + doc = fitz.open(path) + try: + total_pages = min(max_pages, doc.page_count) + for i in range(total_pages): + page = doc.load_page(i) + pix = page.get_pixmap(dpi=200) + img = Image.frombytes("RGB", (pix.width, pix.height), pix.samples) + try: + text = pytesseract.image_to_string(img, lang="spa") or "" + except Exception: + try: + text = pytesseract.image_to_string(img, lang="eng") or "" + if not ocr_error: + ocr_error = "tesseract_lang_missing_spa" + except Exception: + return "", "tesseract_lang_missing" + text_parts.append(text) + finally: + doc.close() + + return "\n".join(text_parts), ocr_error + + +def extract_name(lines, norm_lines): + keys = ["DATOS DEL USUARIO", "DATOS DEL PACIENTE"] + 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 + + skip_tokens = [ + "1ER APELLIDO", + "2DO APELLIDO", + "1ER NOMBRE", + "2DO NOMBRE", + "TIPO DOCUMENTO", + "DOCUMENTO DE IDENTIFICACION", + "REGISTRO CIVIL", + "TARJETA", + "CEDULA", + "NUIP", + ] + + for j in range(idx + 1, min(idx + 6, len(lines))): + nline = norm_lines[j] + if any(token in nline for token in skip_tokens): + 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: + 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 + 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 + + +def detect_format(norm_text, norm_lines): + if "ANEXO TECNICO" in norm_text or "SOLICITUD DE AUTORIZACION" in norm_text: + return "ANEXO_TECNICO" + for line in norm_lines: + if "ATENCION INICIAL DE URGENCIAS" in line: + return "ANEXO_URGENCIAS" + return "DESCONOCIDO" + + +def build_response(text, ocr_used, ocr_available, ocr_error): + lines = [line.strip() for line in (text or "").split("\n") if line.strip()] + norm_lines = [normalize(line) for line in lines] + norm_text = normalize(text) + + nombre = extract_name(lines, norm_lines) + documento = extract_document(lines, norm_lines) + cup_codigo, cup_desc = extract_cups(lines, norm_lines) + cie_codigo, cie_desc = extract_cie10(lines, norm_lines) + formato = detect_format(norm_text, norm_lines) + + warnings = [] + if not text: + warnings.append("no_text_extracted") + if not cup_codigo: + warnings.append("cups_not_found") + if not cie_codigo: + warnings.append("cie10_not_found") + + return { + "ok": True, + "text_length": len(norm_text), + "ocr_usado": ocr_used, + "ocr_disponible": ocr_available, + "ocr_error": ocr_error or None, + "formato": formato, + "nombre_paciente": nombre, + "numero_documento": documento, + "cup_codigo": cup_codigo, + "cup_descripcion": cup_desc, + "cie10_codigo": cie_codigo, + "cie10_descripcion": cie_desc, + "warnings": warnings, + } + + +def main(): + if len(sys.argv) < 2: + print(json.dumps({"ok": False, "error": "missing_file"}, ensure_ascii=True)) + return + + path = sys.argv[1] + + text = extract_text_pdfplumber(path) + if not text: + text = extract_text_fitz(path) + + normalized_len = len(normalize(text)) + ocr_used = False + ocr_error = "" + ocr_available = is_tesseract_available() + + if normalized_len < 50 and ocr_available: + ocr_text, ocr_error = extract_text_ocr(path) + if ocr_text: + text = ocr_text + ocr_used = True + + response = build_response(text, ocr_used, ocr_available, ocr_error) + print(json.dumps(response, ensure_ascii=True)) + + +if __name__ == "__main__": + main() diff --git a/backend/src/plantilla_autorizacion.js b/backend/src/plantilla_autorizacion.js index 061cead..f474668 100644 --- a/backend/src/plantilla_autorizacion.js +++ b/backend/src/plantilla_autorizacion.js @@ -1436,27 +1436,27 @@ async function crearLibroAutorizacion(a) { sheet.getCell('L21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('L21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('L21').alignment = {"horizontal":"center","vertical":"middle"}; - sheet.getCell('M21').value = "CODIGO INTERNO"; + sheet.getCell('M21').value = "CODIGO CIE-10"; sheet.getCell('M21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('M21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('M21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('N21').value = "CODIGO INTERNO"; + sheet.getCell('N21').value = "DIAGNOSTICO"; sheet.getCell('N21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('N21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('N21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('O21').value = "CODIGO INTERNO"; + sheet.getCell('O21').value = "DIAGNOSTICO"; sheet.getCell('O21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('O21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('O21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('P21').value = "CODIGO INTERNO"; + sheet.getCell('P21').value = "DIAGNOSTICO"; sheet.getCell('P21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('P21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('P21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('Q21').value = "CODIGO INTERNO"; + sheet.getCell('Q21').value = "DIAGNOSTICO"; sheet.getCell('Q21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('Q21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('Q21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('R21').value = "CODIGO INTERNO"; + sheet.getCell('R21').value = "DIAGNOSTICO"; sheet.getCell('R21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('R21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"double"},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('R21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; @@ -1510,27 +1510,27 @@ async function crearLibroAutorizacion(a) { sheet.getCell('L22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('L22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('L22').alignment = {"horizontal":"center","vertical":"middle"}; - sheet.getCell('M22').value = "CODIGO INTERNO"; + sheet.getCell('M22').value = "CODIGO CIE-10"; sheet.getCell('M22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('M22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('M22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('N22').value = "CODIGO INTERNO"; + sheet.getCell('N22').value = "DIAGNOSTICO"; sheet.getCell('N22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('N22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('N22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('O22').value = "CODIGO INTERNO"; + sheet.getCell('O22').value = "DIAGNOSTICO"; sheet.getCell('O22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('O22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('O22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('P22').value = "CODIGO INTERNO"; + sheet.getCell('P22').value = "DIAGNOSTICO"; sheet.getCell('P22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('P22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('P22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('Q22').value = "CODIGO INTERNO"; + sheet.getCell('Q22').value = "DIAGNOSTICO"; sheet.getCell('Q22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('Q22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('Q22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('R22').value = "CODIGO INTERNO"; + sheet.getCell('R22').value = "DIAGNOSTICO"; sheet.getCell('R22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('R22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"double"},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('R22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; @@ -2697,9 +2697,8 @@ async function crearLibroAutorizacion(a) { sheet.getCell(celda).value = nitIps; }); - ['M21', 'N21', 'R21'].forEach(celda => { - sheet.getCell(celda).value = a.interno || ''; - }); + sheet.getCell('M21').value = a.cie10_codigo || ''; + sheet.getCell('N21').value = a.cie10_descripcion || ''; ['N20', 'R20'].forEach(celda => { sheet.getCell(celda).value = a.sexo || ''; @@ -2715,12 +2714,12 @@ async function crearLibroAutorizacion(a) { }); ['M16', 'N16'].forEach(celda => { - sheet.getCell(celda).value = a.departamento || ''; + sheet.getCell(celda).value = a.municipio || ''; }); - // 6) Municipio IPS (Q16:R16) + // 6) Departamento IPS (Q16:R16) ['Q16', 'R16'].forEach(celda => { - sheet.getCell(celda).value = a.municipio || ''; + sheet.getCell(celda).value = a.departamento || ''; }); // 7) Nombre completo paciente (H18:R18) @@ -2755,6 +2754,12 @@ async function crearLibroAutorizacion(a) { if (nivelTexto) cupInfoParts.push(nivelTexto); const cupInfo = cupInfoParts.join(' - '); const observacionBase = 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 = ''; @@ -2763,7 +2768,7 @@ async function crearLibroAutorizacion(a) { } else if (tipoAutorizacion === 'consultas_externas') { tipoServicioTexto = 'Tipo servicio: Consulta externa'; } - const observacion = [cupInfo, tipoServicioTexto, observacionBase] + const observacion = [cupInfo, tipoServicioTexto, 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 3a053db..eabc31b 100644 --- a/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js +++ b/backend/src/plantilla_autorizacion_brigadas_ambulancias_hospitalarios.js @@ -1279,27 +1279,27 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { sheet.getCell('L21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('L21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('L21').alignment = {"horizontal":"center","vertical":"middle"}; - sheet.getCell('M21').value = "CODIGO INTERNO"; + sheet.getCell('M21').value = "CODIGO CIE-10"; sheet.getCell('M21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('M21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('M21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('N21').value = "CODIGO INTERNO"; + sheet.getCell('N21').value = "DIAGNOSTICO"; sheet.getCell('N21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('N21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('N21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('O21').value = "CODIGO INTERNO"; + sheet.getCell('O21').value = "DIAGNOSTICO"; sheet.getCell('O21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('O21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('O21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('P21').value = "CODIGO INTERNO"; + sheet.getCell('P21').value = "DIAGNOSTICO"; sheet.getCell('P21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('P21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('P21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('Q21').value = "CODIGO INTERNO"; + sheet.getCell('Q21').value = "DIAGNOSTICO"; sheet.getCell('Q21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('Q21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('Q21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('R21').value = "CODIGO INTERNO"; + sheet.getCell('R21').value = "DIAGNOSTICO"; sheet.getCell('R21').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('R21').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"double"},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('R21').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; @@ -1347,27 +1347,27 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { sheet.getCell('L22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('L22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('L22').alignment = {"horizontal":"center","vertical":"middle"}; - sheet.getCell('M22').value = "CODIGO INTERNO"; + sheet.getCell('M22').value = "CODIGO CIE-10"; sheet.getCell('M22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('M22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('M22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('N22').value = "CODIGO INTERNO"; + sheet.getCell('N22').value = "DIAGNOSTICO"; sheet.getCell('N22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('N22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('N22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('O22').value = "CODIGO INTERNO"; + sheet.getCell('O22').value = "DIAGNOSTICO"; sheet.getCell('O22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('O22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('O22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('P22').value = "CODIGO INTERNO"; + sheet.getCell('P22').value = "DIAGNOSTICO"; sheet.getCell('P22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('P22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('P22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('Q22').value = "CODIGO INTERNO"; + sheet.getCell('Q22').value = "DIAGNOSTICO"; sheet.getCell('Q22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('Q22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"medium","color":{"argb":"FF000000"}},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('Q22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; - sheet.getCell('R22').value = "CODIGO INTERNO"; + sheet.getCell('R22').value = "DIAGNOSTICO"; sheet.getCell('R22').fill = {"type":"pattern","pattern":"none"}; sheet.getCell('R22').border = {"left":{"style":"medium","color":{"argb":"FF000000"}},"right":{"style":"double"},"top":{"style":"medium","color":{"argb":"FF000000"}},"bottom":{"style":"medium","color":{"argb":"FF000000"}}}; sheet.getCell('R22').alignment = {"horizontal":"center","vertical":"middle","wrapText":true}; @@ -2373,9 +2373,8 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { sheet.getCell(celda).value = nitIps; }); - ['M21', 'N21', 'R21'].forEach((celda) => { - sheet.getCell(celda).value = a.interno || ''; - }); + sheet.getCell('M21').value = a.cie10_codigo || ''; + sheet.getCell('N21').value = a.cie10_descripcion || ''; ['N20', 'R20'].forEach((celda) => { sheet.getCell(celda).value = a.sexo || ''; @@ -2390,11 +2389,11 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { }); ['M16', 'N16'].forEach((celda) => { - sheet.getCell(celda).value = a.departamento || ''; + sheet.getCell(celda).value = a.municipio || ''; }); ['Q16', 'R16'].forEach((celda) => { - sheet.getCell(celda).value = a.municipio || ''; + sheet.getCell(celda).value = a.departamento || ''; }); ['H18', 'I18', 'J18', 'K18', 'L18', 'M18', 'N18', 'O18', 'P18', 'Q18', 'R18'].forEach((celda) => { @@ -2425,7 +2424,13 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) { if (nivelTexto) cupInfoParts.push(nivelTexto); const cupInfo = cupInfoParts.join(' - '); const observacionBase = a.observacion || ''; - const observacion = [cupInfo, observacionBase].filter(Boolean).join(' | '); + const solicitanteNombre = String(a.nombre_solicitante || '').trim(); + const observacionLower = observacionBase.toLowerCase(); + const solicitanteInfo = + solicitanteNombre && !observacionLower.includes('solicitante') + ? 'Solicitante: ' + solicitanteNombre + : ''; + const observacion = [cupInfo, observacionBase, solicitanteInfo].filter(Boolean).join(' | '); ['H31', 'R31'].forEach((celda) => { sheet.getCell(celda).value = observacion; }); diff --git a/backend/src/schema.sql b/backend/src/schema.sql index d6300cb..cfb0fba 100644 --- a/backend/src/schema.sql +++ b/backend/src/schema.sql @@ -180,13 +180,26 @@ CREATE TABLE IF NOT EXISTS autorizacion ( interno text NOT NULL REFERENCES paciente(interno), id_ips integer NOT NULL REFERENCES ips(id_ips), numero_documento_autorizante bigint NOT NULL REFERENCES autorizante(numero_documento), + id_usuario_solicitante integer REFERENCES usuario(id_usuario), fecha_autorizacion date NOT NULL DEFAULT current_date, observacion text, cup_codigo varchar(20), + cie10_codigo varchar(20), + cie10_descripcion text, tipo_autorizacion varchar(50) NOT NULL DEFAULT 'consultas_externas', tipo_servicio varchar(50), + ambito_atencion varchar(20), + numero_orden varchar(50), + archivo_historial_clinico text, + archivo_historial_clinico_nombre text, + archivo_anexo text, + archivo_anexo_nombre text, + estado_entrega varchar(20) NOT NULL DEFAULT 'pendiente_entrega', estado_autorizacion varchar(20) NOT NULL DEFAULT 'pendiente', - version integer NOT NULL DEFAULT 1 + version integer NOT NULL DEFAULT 1, + correo_inpec_pendiente boolean NOT NULL DEFAULT false, + correo_inpec_enviado boolean NOT NULL DEFAULT false, + correo_inpec_respuesta boolean NOT NULL DEFAULT false ); DO $$ @@ -202,6 +215,32 @@ BEGIN END IF; END $$; +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'autorizacion_estado_entrega_chk' + ) THEN + ALTER TABLE autorizacion + ADD CONSTRAINT autorizacion_estado_entrega_chk + CHECK (estado_entrega IN ('pendiente_entrega', 'entregado', 'programado', 'atendido', 'cancelado')); + END IF; +END $$; + +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'autorizacion_ambito_atencion_chk' + ) THEN + ALTER TABLE autorizacion + ADD CONSTRAINT autorizacion_ambito_atencion_chk + CHECK (ambito_atencion IN ('intramural', 'extramural')); + END IF; +END $$; + INSERT INTO consecutivo_autorizacion (id, codigo) VALUES (1, COALESCE((SELECT MAX(numero_autorizacion) FROM autorizacion), 'UTUSCPGB00')) ON CONFLICT (id) DO NOTHING; @@ -229,11 +268,17 @@ CREATE TABLE IF NOT EXISTS autorizacion_version ( version INTEGER NOT NULL, id_ips INTEGER NOT NULL, numero_documento_autorizante BIGINT NOT NULL, + id_usuario_solicitante INTEGER, fecha_autorizacion DATE, observacion TEXT, cup_codigo VARCHAR(20), + cie10_codigo VARCHAR(20), + cie10_descripcion TEXT, tipo_autorizacion VARCHAR(50), tipo_servicio VARCHAR(50), + ambito_atencion VARCHAR(20), + numero_orden VARCHAR(50), + estado_entrega VARCHAR(20), fecha_version TIMESTAMP NOT NULL DEFAULT NOW() ); diff --git a/backend/src/server.js b/backend/src/server.js index ef588fc..3114c44 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -111,6 +111,64 @@ const normalizeSearch = (value) => .toUpperCase() .trim(); +const isPdfFile = (file) => { + if (!file) return false; + const mime = String(file.mimetype || '').toLowerCase(); + const ext = path.extname(file.originalname || '').toLowerCase(); + return mime === 'application/pdf' || ext === '.pdf'; +}; + +const buildAutorizacionUploadName = (prefix) => { + const stamp = Date.now(); + const rand = crypto.randomBytes(4).toString('hex'); + return `${prefix}_${stamp}_${rand}.pdf`; +}; + +const resolveUploadPath = (relativePath) => { + if (!relativePath) return ''; + return path.join(__dirname, ...String(relativePath).split('/')); +}; + +const parseJsonOutput = (stdout) => { + const trimmed = String(stdout || '').trim(); + if (!trimmed) { + throw new Error('Salida vacia del extractor'); + } + const start = trimmed.indexOf('{'); + const end = trimmed.lastIndexOf('}'); + if (start === -1 || end === -1 || end <= start) { + throw new Error('Salida no es JSON valido'); + } + const jsonText = trimmed.slice(start, end + 1); + return JSON.parse(jsonText); +}; + +const extraerDatosPdfAutorizacion = (filePath) => { + const scriptPath = path.join(__dirname, 'extraer_autorizacion_pdf.py'); + const pythonPath = process.env.PYTHON_PATH || 'python'; + + return new Promise((resolve, reject) => { + execFile( + pythonPath, + [scriptPath, filePath], + { cwd: __dirname }, + (error, stdout, stderr) => { + if (error) { + return reject( + new Error(stderr?.trim() || error.message || 'Error ejecutando extractor') + ); + } + try { + const data = parseJsonOutput(stdout); + resolve(data); + } catch (parseError) { + reject(parseError); + } + } + ); + }); +}; + const scheduleJobCleanup = (jobId) => { setTimeout(async () => { const job = jobs.get(jobId); @@ -232,18 +290,24 @@ const ensureCupsTables = async () => { }; const ensureAutorizacionVersionTables = async () => { - await pool.query(` - CREATE TABLE IF NOT EXISTS autorizacion_version ( - id SERIAL PRIMARY KEY, - numero_autorizacion VARCHAR(50) NOT NULL, - version INTEGER NOT NULL, - id_ips INTEGER NOT NULL, - numero_documento_autorizante BIGINT NOT NULL, - fecha_autorizacion DATE, - observacion TEXT, - cup_codigo VARCHAR(20), + await pool.query(` + CREATE TABLE IF NOT EXISTS autorizacion_version ( + id SERIAL PRIMARY KEY, + numero_autorizacion VARCHAR(50) NOT NULL, + version INTEGER NOT NULL, + id_ips INTEGER NOT NULL, + numero_documento_autorizante BIGINT NOT NULL, + id_usuario_solicitante INTEGER, + fecha_autorizacion DATE, + observacion TEXT, + cup_codigo VARCHAR(20), + cie10_codigo VARCHAR(20), + cie10_descripcion TEXT, tipo_autorizacion VARCHAR(50), tipo_servicio VARCHAR(50), + ambito_atencion VARCHAR(20), + numero_orden VARCHAR(50), + estado_entrega VARCHAR(20), fecha_version TIMESTAMP NOT NULL DEFAULT NOW() ); `); @@ -254,6 +318,157 @@ const ensureAutorizacionVersionTables = async () => { `); }; +const ensureAutorizacionExtras = async () => { + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS cie10_codigo VARCHAR(20); + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS cie10_descripcion TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS ambito_atencion VARCHAR(20); + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS id_usuario_solicitante INTEGER; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS numero_orden VARCHAR(50); + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS archivo_historial_clinico TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS archivo_historial_clinico_nombre TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS archivo_anexo TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS archivo_anexo_nombre TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS estado_entrega VARCHAR(20) NOT NULL DEFAULT 'pendiente_entrega'; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS correo_inpec_pendiente BOOLEAN NOT NULL DEFAULT false; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS correo_inpec_enviado BOOLEAN NOT NULL DEFAULT false; + `); + + await pool.query(` + ALTER TABLE autorizacion + ADD COLUMN IF NOT EXISTS correo_inpec_respuesta BOOLEAN NOT NULL DEFAULT false; + `); + + await pool.query(` + UPDATE autorizacion + SET correo_inpec_pendiente = false + WHERE correo_inpec_pendiente IS NULL; + `); + + await pool.query(` + UPDATE autorizacion + SET correo_inpec_enviado = false + WHERE correo_inpec_enviado IS NULL; + `); + + await pool.query(` + UPDATE autorizacion + SET correo_inpec_respuesta = false + WHERE correo_inpec_respuesta IS NULL; + `); + + await pool.query(` + UPDATE autorizacion + SET estado_entrega = 'pendiente_entrega' + WHERE estado_entrega IS NULL; + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS cie10_codigo VARCHAR(20); + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS cie10_descripcion TEXT; + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS ambito_atencion VARCHAR(20); + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS id_usuario_solicitante INTEGER; + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS numero_orden VARCHAR(50); + `); + + await pool.query(` + ALTER TABLE autorizacion_version + ADD COLUMN IF NOT EXISTS estado_entrega VARCHAR(20); + `); + + await pool.query(` + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'autorizacion_ambito_atencion_chk' + ) THEN + ALTER TABLE autorizacion + ADD CONSTRAINT autorizacion_ambito_atencion_chk + CHECK (ambito_atencion IN ('intramural', 'extramural')); + END IF; + END $$; + `); + + await pool.query(` + DO $$ + BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM pg_constraint + WHERE conname = 'autorizacion_estado_entrega_chk' + ) THEN + ALTER TABLE autorizacion + ADD CONSTRAINT autorizacion_estado_entrega_chk + CHECK (estado_entrega IN ('pendiente_entrega', 'entregado', 'programado', 'atendido', 'cancelado')); + END IF; + END $$; + `); +}; + const ensureIpsConvenio = async () => { await pool.query(` ALTER TABLE ips @@ -547,6 +762,26 @@ const puedeDescargarPdfAutorizacion = async (req, res, next) => { } }; +const puedeActualizarEstadoEntrega = (req, res, next) => { + const rol = req.usuario?.nombre_rol; + if (rol === 'administrador' || rol === 'administrativo_sede') { + return next(); + } + return res.status(403).json({ + error: 'No tienes permisos para actualizar estado de entrega', + }); +}; + +const puedeDescargarZipAutorizaciones = (req, res, next) => { + const rol = req.usuario?.nombre_rol; + if (rol === 'administrador' || rol === 'administrativo_sede') { + return next(); + } + return res.status(403).json({ + error: 'No tienes permisos para descargar autorizaciones', + }); +}; + // Middleware para verificar acceso por sede const verificarAccesoSede = async (req, res, next) => { // Si es administrador, tiene acceso a todo @@ -825,6 +1060,182 @@ app.post( } ); +app.post( + '/api/autorizaciones/autorrellenar', + verificarToken, + puedeGenerarAutorizaciones, + upload.single('archivo'), + async (req, res) => { + try { + if (!req.file) { + return res.status(400).json({ error: 'No se recibio archivo PDF' }); + } + if (!isPdfFile(req.file)) { + return res.status(400).json({ error: 'El archivo debe ser PDF' }); + } + + const tempDir = path.join(os.tmpdir(), 'saludut_pdf'); + await fsPromises.mkdir(tempDir, { recursive: true }); + const tempName = `autorrelleno_${Date.now()}_${crypto.randomBytes(4).toString('hex')}.pdf`; + const tempPath = path.join(tempDir, tempName); + await fsPromises.writeFile(tempPath, req.file.buffer); + + try { + const data = await extraerDatosPdfAutorizacion(tempPath); + return res.json(data); + } finally { + await safeUnlink(tempPath); + } + } catch (error) { + console.error('Error extrayendo datos PDF:', error.message); + return res.status(500).json({ error: 'Error extrayendo datos del PDF' }); + } + } +); + +app.post( + '/api/autorizaciones/:numero_autorizacion/archivos', + verificarToken, + puedeGenerarAutorizaciones, + upload.fields([ + { name: 'historial_clinico', maxCount: 1 }, + { name: 'anexo', maxCount: 1 }, + ]), + async (req, res) => { + const { numero_autorizacion } = req.params; + + if (!numero_autorizacion) { + return res.status(400).json({ error: 'numero_autorizacion es obligatorio' }); + } + + const historialFile = req.files?.historial_clinico?.[0] || null; + const anexoFile = req.files?.anexo?.[0] || null; + + if (!historialFile || !anexoFile) { + return res.status(400).json({ error: 'Debes enviar historial_clinico y anexo' }); + } + + if (historialFile && !isPdfFile(historialFile)) { + return res.status(400).json({ error: 'El historial clinico debe ser PDF' }); + } + if (anexoFile && !isPdfFile(anexoFile)) { + return res.status(400).json({ error: 'El anexo debe ser PDF' }); + } + + try { + const authRes = await pool.query( + ` + SELECT + tipo_autorizacion, + tipo_servicio, + archivo_historial_clinico, + archivo_anexo + FROM autorizacion + WHERE numero_autorizacion = $1 + `, + [numero_autorizacion] + ); + + if (authRes.rows.length === 0) { + return res.status(404).json({ error: 'Autorizacion no encontrada' }); + } + + const autorizacion = authRes.rows[0]; + const tipoAutorizacion = String(autorizacion.tipo_autorizacion || '').toLowerCase(); + const tipoServicio = String(autorizacion.tipo_servicio || '').toLowerCase(); + + if ( + tipoAutorizacion !== 'brigadas_ambulancias_hospitalarios' || + tipoServicio !== 'hospitalarios' + ) { + return res.status(400).json({ + error: 'Solo se permiten archivos en autorizaciones hospitalarias', + }); + } + + const baseDir = path.join(__dirname, 'uploads', 'autorizaciones', numero_autorizacion); + await fsPromises.mkdir(baseDir, { recursive: true }); + + const updates = []; + const values = []; + let idx = 1; + const oldFiles = []; + + if (historialFile) { + const fileName = buildAutorizacionUploadName('HC'); + const relativePath = path.posix.join( + 'uploads', + 'autorizaciones', + numero_autorizacion, + fileName + ); + const fullPath = resolveUploadPath(relativePath); + await fsPromises.writeFile(fullPath, historialFile.buffer); + + updates.push(`archivo_historial_clinico = $${idx++}`); + values.push(relativePath); + updates.push(`archivo_historial_clinico_nombre = $${idx++}`); + values.push(historialFile.originalname || fileName); + + if (autorizacion.archivo_historial_clinico) { + oldFiles.push(resolveUploadPath(autorizacion.archivo_historial_clinico)); + } + } + + if (anexoFile) { + const fileName = buildAutorizacionUploadName('ANEXO'); + const relativePath = path.posix.join( + 'uploads', + 'autorizaciones', + numero_autorizacion, + fileName + ); + const fullPath = resolveUploadPath(relativePath); + await fsPromises.writeFile(fullPath, anexoFile.buffer); + + updates.push(`archivo_anexo = $${idx++}`); + values.push(relativePath); + updates.push(`archivo_anexo_nombre = $${idx++}`); + values.push(anexoFile.originalname || fileName); + + if (autorizacion.archivo_anexo) { + oldFiles.push(resolveUploadPath(autorizacion.archivo_anexo)); + } + } + + if (updates.length === 0) { + return res.status(400).json({ error: 'No hay archivos para guardar' }); + } + + values.push(numero_autorizacion); + const sql = ` + UPDATE autorizacion + SET ${updates.join(', ')} + WHERE numero_autorizacion = $${idx} + RETURNING numero_autorizacion, + archivo_historial_clinico, + archivo_historial_clinico_nombre, + archivo_anexo, + archivo_anexo_nombre; + `; + + const updateRes = await pool.query(sql, values); + + for (const filePath of oldFiles) { + await safeUnlink(filePath); + } + + return res.json({ + mensaje: 'Archivos cargados correctamente', + autorizacion: updateRes.rows[0], + }); + } catch (error) { + console.error('Error subiendo archivos de autorizacion:', error); + return res.status(500).json({ error: 'Error guardando archivos' }); + } + } +); + // MIDDLEWARES GLOBALES (para el resto de las rutas que sí usan JSON) app.use( express.json({ @@ -1036,6 +1447,33 @@ const parseServicio = (value) => { return { tipo_autorizacion: 'consultas_externas', tipo_servicio: null }; }; +const parseAmbito = (value) => { + const raw = normalizeSearch(value); + if (!raw) return ''; + if (raw.includes('INTRA')) return 'intramural'; + if (raw.includes('EXTRA')) return 'extramural'; + return ''; +}; + +const ESTADOS_ENTREGA = [ + 'pendiente_entrega', + 'entregado', + 'programado', + 'atendido', + 'cancelado', +]; + +const parseEstadoEntrega = (value) => { + const raw = normalizeSearch(value); + if (!raw) return ''; + if (raw.includes('PEND')) return 'pendiente_entrega'; + if (raw.includes('ENTREG')) return 'entregado'; + if (raw.includes('PROGRAM')) return 'programado'; + if (raw.includes('ATEND')) return 'atendido'; + if (raw.includes('CANCEL')) return 'cancelado'; + return ''; +}; + const splitNombrePaciente = (nombreCompleto) => { const limpio = String(nombreCompleto || '').trim(); if (!limpio) { @@ -1109,6 +1547,14 @@ async function procesarExcelIps(inputFilePath) { return getExcelCellText(row.getCell(col).value); }; + const getValueMulti = (row, keys) => { + for (const key of keys) { + const value = getValue(row, key); + if (value) return value; + } + return ''; + }; + const resumen = { total: 0, insertados: 0, @@ -1122,7 +1568,7 @@ async function procesarExcelIps(inputFilePath) { const codigoKeys = new Set(); const nombreKeys = new Set(); - const client = await pool.connect(); + const client = await pool.connect(); try { await client.query('BEGIN'); @@ -1457,6 +1903,14 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { return getExcelCellText(row.getCell(col).value); }; + const getValueMulti = (row, keys) => { + for (const key of keys) { + const value = getValue(row, key); + if (value) return value; + } + return ''; + }; + const autorizanteRes = await pool.query( ` SELECT numero_documento @@ -1479,11 +1933,15 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { sin_paciente: 0, sin_cups: 0, sin_ips: 0, + sin_diagnostico: 0, + sin_ambito: 0, cups_no_cubiertos: 0, ips_sin_convenio: 0, errores: [], }; + const solicitanteId = usuario?.id_usuario ? Number(usuario.id_usuario) : null; + const cupCache = new Map(); const pacienteCache = new Map(); const ipsCache = new Map(); @@ -1519,10 +1977,28 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { for (let i = 2; i <= sheet.rowCount; i++) { const row = sheet.getRow(i); - const cedula = getValue(row, 'CEDULA'); - const procedimiento = getValue(row, 'PROCEDIMIENTOQUESEAUTORIZA'); + const cedula = getValueMulti(row, [ + 'CEDULA', + 'NUMERODEDOCUMENTOPPL', + 'NUMERODOCUMENTOPPL', + 'DOCUMENTOPPL', + 'NUMERODOCUMENTO', + ]); + const cupsRaw = getValueMulti(row, [ + 'CUPS', + 'CODIGOCUPS', + 'CODIGOPROCEDIMIENTO', + 'PROCEDIMIENTOQUESEAUTORIZA', + 'PROCEDIMIENTO', + ]); + const procedimiento = getValueMulti(row, [ + 'PROCEDIMIENTOQUESEAUTORIZA', + 'PROCEDIMIENTO', + 'DESCRIPCIONCUPS', + 'DESCRIPCIONPROCEDIMIENTO', + ]); - if (!cedula && !procedimiento) { + if (!cedula && !cupsRaw && !procedimiento) { continue; } @@ -1563,7 +2039,79 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { continue; } - const cupCodigo = extractCupCodigo(procedimiento); + const cie10CodigoRaw = getValueMulti(row, [ + 'CIE10', + 'CIE', + 'CODIGOCIE10', + 'CIE10CODIGO', + 'CODIGODIAGNOSTICO', + 'DIAGNOSTICOCIE10', + ]); + const cie10DescRaw = getValueMulti(row, [ + 'DIAGNOSTICO', + 'DIAGNOSTICOCOMPLETO', + 'DESCRIPCIONDIAGNOSTICO', + 'DIAGNOSTICOPRINCIPAL', + 'DIAGNOSTICO1', + 'DESCRIPCIONCIE10', + 'DECRIPCIONCIE10', + ]); + const cie10Codigo = String(cie10CodigoRaw || '').trim().toUpperCase(); + const cie10Descripcion = String(cie10DescRaw || '').trim(); + if (!cie10Codigo || !cie10Descripcion) { + resumen.omitidas += 1; + resumen.sin_diagnostico += 1; + if (resumen.errores.length < 50) { + resumen.errores.push({ + fila: i, + error: 'Diagnostico CIE-10 incompleto', + }); + } + continue; + } + + const ambitoRaw = getValueMulti(row, [ + 'AMBITO', + 'AMBITOATENCION', + 'INTRAMURALEXTRAMURAL', + 'TIPOAMBITO', + 'TIPOATENCION', + 'TIPODEORDEN', + ]); + const ambitoAtencion = parseAmbito(ambitoRaw); + if (!ambitoAtencion) { + resumen.omitidas += 1; + resumen.sin_ambito += 1; + if (resumen.errores.length < 50) { + resumen.errores.push({ + fila: i, + error: 'Ambito intramural/extramural invalido', + }); + } + continue; + } + + const numeroOrdenRaw = getValueMulti(row, [ + 'NUMERODEORDEN', + 'NEMRODEORDEN', + 'NUMEROORDEN', + 'NROORDEN', + 'NUMERODELAORDEN', + ]); + const numeroOrden = String(numeroOrdenRaw || '').trim(); + const numeroOrdenFinal = + ambitoAtencion === 'intramural' && numeroOrden ? numeroOrden : null; + + const estadoEntregaRaw = getValueMulti(row, [ + 'ESTADODEENTREGA', + 'ESTADOENTREGA', + 'ESTADO', + 'VALIDADOR', + ]); + const estadoEntrega = + parseEstadoEntrega(estadoEntregaRaw) || 'pendiente_entrega'; + + const cupCodigo = extractCupCodigo(cupsRaw || procedimiento); if (!cupCodigo) { resumen.omitidas += 1; resumen.sin_cups += 1; @@ -1587,8 +2135,19 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { resumen.cups_no_cubiertos += 1; } - const nitIps = getValue(row, 'NITIPS'); - const hospital = getValue(row, 'HOSPITALCLINICA'); + const nitIps = getValueMulti(row, [ + 'NITIPS', + 'NIT', + 'NITIPSREMITIDO', + 'NITIPSREMITIDA', + ]); + const hospital = getValueMulti(row, [ + 'HOSPITALCLINICA', + 'NOMBREIPS', + 'NOMBREIPSREMITIDO', + 'NOMBREDEIPSREMITIDO', + 'IPSREMITIDO', + ]); const departamentoExcel = getValue(row, 'DEPARTAMENTO') || getValue(row, 'BOGOTA') || @@ -1741,9 +2300,15 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { } const observaciones = []; - const obs1 = getValue(row, 'OBSERVACIONES'); + const obs1 = getValueMulti(row, [ + 'OBSERVACIONES', + 'SUBESPECIALIDADOBSERVACIONES', + ]); const obs2 = getValue(row, 'OBSERVACIONES1'); - const solicitante = getValue(row, 'QUIENAUTORIZA'); + const solicitante = getValueMulti(row, [ + 'QUIENAUTORIZA', + 'RESPONSABLE', + ]); if (obs1) observaciones.push(obs1); if (obs2) observaciones.push(obs2); @@ -1762,45 +2327,57 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { await client.query('SAVEPOINT row_sp'); try { - const insertRes = await client.query( - ` - INSERT INTO autorizacion - (interno, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio, estado_autorizacion) - VALUES ($1, $2, $3, COALESCE($4::date, current_date), $5, $6, $7, $8, 'pendiente') - RETURNING numero_autorizacion, fecha_autorizacion, version; - `, - [ - interno, - ipsInfo.id_ips, - autorizanteDefault, - null, - observacionFinal, - cupCodigo, - servicioInfo.tipo_autorizacion, - servicioInfo.tipo_servicio, - ] - ); + const insertRes = await client.query( + ` + INSERT INTO autorizacion + (interno, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega, estado_autorizacion) + VALUES ($1, $2, $3, $4, COALESCE($5::date, current_date), $6, $7, $8, $9, $10, $11, $12, $13, $14, 'pendiente') + RETURNING numero_autorizacion, fecha_autorizacion, version; + `, + [ + interno, + ipsInfo.id_ips, + autorizanteDefault, + solicitanteId, + null, + observacionFinal, + cupCodigo, + cie10Codigo, + cie10Descripcion, + servicioInfo.tipo_autorizacion, + servicioInfo.tipo_servicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntrega, + ] + ); const nuevaAut = insertRes.rows[0]; await client.query( - ` - INSERT INTO autorizacion_version - (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); - `, - [ - nuevaAut.numero_autorizacion, - nuevaAut.version || 1, - ipsInfo.id_ips, - autorizanteDefault, - nuevaAut.fecha_autorizacion || null, - observacionFinal, - cupCodigo, - servicioInfo.tipo_autorizacion, - servicioInfo.tipo_servicio, - ] - ); + ` + INSERT INTO autorizacion_version + (numero_autorizacion, version, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); + `, + [ + nuevaAut.numero_autorizacion, + nuevaAut.version || 1, + ipsInfo.id_ips, + autorizanteDefault, + solicitanteId, + nuevaAut.fecha_autorizacion || null, + observacionFinal, + cupCodigo, + cie10Codigo, + cie10Descripcion, + servicioInfo.tipo_autorizacion, + servicioInfo.tipo_servicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntrega, + ] + ); await client.query('RELEASE SAVEPOINT row_sp'); resumen.creadas += 1; @@ -1844,33 +2421,39 @@ async function generarPdfAutorizacionYObtenerPath( a.fecha_autorizacion, a.observacion, a.cup_codigo, + a.cie10_codigo, + a.cie10_descripcion, a.tipo_autorizacion, a.tipo_servicio, + a.ambito_atencion, a.version, p.*, ips.nombre_ips, ips.nit, ips.direccion, - ips.municipio, - ips.departamento, - aut.nombre AS nombre_autorizante, - aut.cargo AS cargo_autorizante, - aut.telefono AS tel_autorizante, - e.nombre_establecimiento, - e.epc_departamento, + ips.municipio, + ips.departamento, + aut.nombre AS nombre_autorizante, + u.nombre_completo AS nombre_solicitante, + aut.cargo AS cargo_autorizante, + aut.telefono AS tel_autorizante, + e.nombre_establecimiento, + e.epc_departamento, cc.descripcion AS cup_descripcion, cc.nivel AS cup_nivel, cc.especialidad AS cup_especialidad FROM autorizacion a JOIN paciente p ON a.interno = p.interno - JOIN ips - ON a.id_ips = ips.id_ips - JOIN autorizante aut - ON a.numero_documento_autorizante = aut.numero_documento - JOIN ingreso i - ON a.interno = i.interno - JOIN establecimiento e + JOIN ips + ON a.id_ips = ips.id_ips + JOIN autorizante aut + ON a.numero_documento_autorizante = aut.numero_documento + LEFT JOIN usuario u + ON a.id_usuario_solicitante = u.id_usuario + JOIN ingreso i + ON a.interno = i.interno + JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo @@ -1888,8 +2471,11 @@ async function generarPdfAutorizacionYObtenerPath( av.fecha_autorizacion, av.observacion, av.cup_codigo, + av.cie10_codigo, + av.cie10_descripcion, av.tipo_autorizacion, av.tipo_servicio, + av.ambito_atencion, av.version AS version, p.*, ips.nombre_ips, @@ -1898,6 +2484,7 @@ async function generarPdfAutorizacionYObtenerPath( ips.municipio, ips.departamento, aut.nombre AS nombre_autorizante, + u.nombre_completo AS nombre_solicitante, aut.cargo AS cargo_autorizante, aut.telefono AS tel_autorizante, e.nombre_establecimiento, @@ -1914,6 +2501,8 @@ async function generarPdfAutorizacionYObtenerPath( ON av.id_ips = ips.id_ips JOIN autorizante aut ON av.numero_documento_autorizante = aut.numero_documento + LEFT JOIN usuario u + ON COALESCE(av.id_usuario_solicitante, a.id_usuario_solicitante) = u.id_usuario JOIN ingreso i ON a.interno = i.interno JOIN establecimiento e @@ -1998,15 +2587,64 @@ async function generarPdfAutorizacionYObtenerPath( return pdfPath; // devolvemos ruta del PDF } -async function generarZipAutorizacionesPorFecha(fechaInicio, fechaFin, jobId) { +async function generarZipAutorizacionesPorFecha( + fechaInicio, + fechaFin, + jobId, + { establecimiento, ambito, ips, soloAutorizadas } = {} +) { + const establecimientoFiltro = String(establecimiento || '').trim(); + const ambitoFiltro = String(ambito || '').trim().toLowerCase(); + const ipsFiltro = String(ips || '').trim(); + const ambitosPermitidos = ['intramural', 'extramural']; + if (ambitoFiltro && !ambitosPermitidos.includes(ambitoFiltro)) { + const err = new Error('Ambito invalido'); + err.code = 'BAD_REQUEST'; + throw err; + } + + const whereParts = ['a.fecha_autorizacion BETWEEN $1 AND $2']; + const params = [fechaInicio, fechaFin]; + const joinParts = []; + if (soloAutorizadas) { + whereParts.push("COALESCE(a.estado_autorizacion, 'pendiente') = 'autorizado'"); + } + if (establecimientoFiltro) { + joinParts.push(` + JOIN ingreso i ON a.interno = i.interno + JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento + `); + } + if (ipsFiltro) { + joinParts.push('JOIN ips ON a.id_ips = ips.id_ips'); + } + + if (establecimientoFiltro) { + params.push(`%${establecimientoFiltro}%`); + whereParts.push(`(e.codigo_establecimiento ILIKE $${params.length} OR e.nombre_establecimiento ILIKE $${params.length})`); + } + + if (ambitoFiltro) { + params.push(ambitoFiltro); + whereParts.push(`a.ambito_atencion = $${params.length}`); + } + + if (ipsFiltro) { + params.push(`%${ipsFiltro}%`); + whereParts.push( + `(ips.nombre_ips ILIKE $${params.length} OR ips.nit ILIKE $${params.length} OR ips.codigo_ips ILIKE $${params.length})` + ); + } + const sqlNumeros = ` SELECT a.numero_autorizacion FROM autorizacion a - WHERE a.fecha_autorizacion BETWEEN $1 AND $2 + ${joinParts.join('\n')} + WHERE ${whereParts.join(' AND ')} ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC `; - const { rows } = await pool.query(sqlNumeros, [fechaInicio, fechaFin]); + const { rows } = await pool.query(sqlNumeros, params); if (rows.length === 0) { const err = new Error('No hay autorizaciones en ese rango de fechas.'); err.code = 'EMPTY_RANGE'; @@ -2030,8 +2668,11 @@ async function generarZipAutorizacionesPorFecha(fechaInicio, fechaFin, jobId) { a.fecha_autorizacion, a.observacion, a.cup_codigo, + a.cie10_codigo, + a.cie10_descripcion, a.tipo_autorizacion, a.tipo_servicio, + a.ambito_atencion, p.*, ips.nombre_ips, ips.nit, @@ -2039,6 +2680,7 @@ async function generarZipAutorizacionesPorFecha(fechaInicio, fechaFin, jobId) { ips.municipio, ips.departamento, aut.nombre AS nombre_autorizante, + u.nombre_completo AS nombre_solicitante, aut.cargo AS cargo_autorizante, aut.telefono AS tel_autorizante, e.nombre_establecimiento, @@ -2051,6 +2693,8 @@ async function generarZipAutorizacionesPorFecha(fechaInicio, fechaFin, jobId) { JOIN ips ON a.id_ips = ips.id_ips JOIN autorizante aut ON a.numero_documento_autorizante = aut.numero_documento + LEFT JOIN usuario u + ON a.id_usuario_solicitante = u.id_usuario JOIN ingreso i ON a.interno = i.interno JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento @@ -2204,13 +2848,18 @@ const crearJobPdfHandler = async (req, res) => { return res.status(202).json(sanitizeJob(job)); }; -const crearJobZipHandler = async (req, res) => { - const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio; - const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin; + const crearJobZipHandler = async (req, res) => { + const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio; + const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin; + const establecimiento = req.body?.establecimiento || req.query?.establecimiento; + const ambito = req.body?.ambito || req.query?.ambito; + const ips = req.body?.ips || req.query?.ips; + const esAdmin = req.usuario?.nombre_rol === 'administrador'; + const soloAutorizadas = !esAdmin; - if (!fecha_inicio || !fecha_fin) { - return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' }); - } + if (!fecha_inicio || !fecha_fin) { + return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' }); + } const jobId = createJobId(); const job = { @@ -2223,11 +2872,16 @@ const crearJobZipHandler = async (req, res) => { jobs.set(jobId, job); - enqueueJob(job, async () => { - const zipPath = await generarZipAutorizacionesPorFecha(fecha_inicio, fecha_fin, jobId); - return { - ok: true, - mensaje: 'ZIP generado correctamente', + enqueueJob(job, async () => { + const zipPath = await generarZipAutorizacionesPorFecha(fecha_inicio, fecha_fin, jobId, { + establecimiento, + ambito, + ips, + soloAutorizadas, + }); + return { + ok: true, + mensaje: 'ZIP generado correctamente', filePath: zipPath, fileName: `autorizaciones_${fecha_inicio}_${fecha_fin}.zip`, contentType: 'application/zip', @@ -2241,8 +2895,8 @@ const crearJobZipHandler = async (req, res) => { app.post('/api/jobs/autorizacion-pdf', verificarToken, puedeDescargarPdfAutorizacion, crearJobPdfHandler); app.get('/api/jobs/autorizacion-pdf', verificarToken, puedeDescargarPdfAutorizacion, crearJobPdfHandler); -app.post('/api/jobs/autorizaciones-zip', verificarToken, esAdministrador, crearJobZipHandler); -app.get('/api/jobs/autorizaciones-zip', verificarToken, esAdministrador, crearJobZipHandler); +app.post('/api/jobs/autorizaciones-zip', verificarToken, puedeDescargarZipAutorizaciones, crearJobZipHandler); +app.get('/api/jobs/autorizaciones-zip', verificarToken, puedeDescargarZipAutorizaciones, crearJobZipHandler); // CUPS cubiertos app.get('/api/cups-cubiertos', verificarToken, async (req, res) => { @@ -2337,7 +2991,11 @@ app.get('/api/pacientes', verificarToken, async (req, res) => { e.epc_ciudad, i.estado, i.fecha_ingreso, - i.tiempo_reclusion + CASE + WHEN i.fecha_ingreso IS NOT NULL + THEN EXTRACT(YEAR FROM age(current_date, i.fecha_ingreso))::int + ELSE NULL + END AS tiempo_reclusion FROM paciente p LEFT JOIN ingreso i ON p.interno = i.interno LEFT JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento @@ -2697,8 +3355,13 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn observacion, fecha_autorizacion, cup_codigo, + cie10_codigo, + cie10_descripcion, tipo_autorizacion, tipo_servicio, + ambito_atencion, + numero_orden, + estado_entrega, } = req.body; if (!interno || !id_ips || !numero_documento_autorizante) { @@ -2712,6 +3375,40 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn return res.status(400).json({ error: 'cup_codigo es obligatorio' }); } + const cie10Codigo = String(cie10_codigo || '').trim().toUpperCase(); + const cie10Descripcion = String(cie10_descripcion || '').trim(); + if (!cie10Codigo) { + return res.status(400).json({ error: 'cie10_codigo es obligatorio' }); + } + if (!cie10Descripcion) { + 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 + ? parseEstadoEntrega(estadoEntregaInput) + : ''; + if (estadoEntregaInput && !estadoEntregaParsed) { + return res.status(400).json({ error: 'estado_entrega invalido' }); + } + const estadoEntregaFinal = estadoEntregaParsed || 'pendiente_entrega'; + const tipoAutorizacion = String(tipo_autorizacion || 'consultas_externas') .trim() .toLowerCase(); @@ -2737,59 +3434,78 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn // ... aquí va tu lógica de permisos por rol / sede ... + const solicitanteId = req.usuario?.id_usuario ? Number(req.usuario.id_usuario) : null; const client = await pool.connect(); try { await client.query('BEGIN'); - const sql = ` - INSERT INTO autorizacion - ( + const sql = ` + INSERT INTO autorizacion + ( + interno, + id_ips, + numero_documento_autorizante, + id_usuario_solicitante, + fecha_autorizacion, + observacion, + cup_codigo, + cie10_codigo, + cie10_descripcion, + tipo_autorizacion, + tipo_servicio, + ambito_atencion, + numero_orden, + estado_entrega + ) + VALUES ($1, $2, $3, $4, COALESCE($5::date, current_date), $6, $7, $8, $9, $10, $11, $12, $13, $14) + RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega, version, estado_autorizacion; + `; + + const params = [ interno, id_ips, numero_documento_autorizante, - fecha_autorizacion, - observacion, - cup_codigo, - tipo_autorizacion, - tipo_servicio - ) - VALUES ($1, $2, $3, COALESCE($4::date, current_date), $5, $6, $7, $8) - RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, tipo_autorizacion, tipo_servicio, version, estado_autorizacion; - `; - - const params = [ - interno, - id_ips, - numero_documento_autorizante, - fecha_autorizacion || null, - observacion || null, - cupCodigo, + solicitanteId, + fecha_autorizacion || null, + observacion || null, + cupCodigo, + cie10Codigo, + cie10Descripcion, tipoAutorizacion, - tipoServicio, - ]; + tipoServicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntregaFinal, + ]; const { rows } = await client.query(sql, params); const nuevaAutorizacion = rows[0]; await client.query( - ` - INSERT INTO autorizacion_version - (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); - `, - [ - nuevaAutorizacion.numero_autorizacion, - nuevaAutorizacion.version || 1, - id_ips, - numero_documento_autorizante, - nuevaAutorizacion.fecha_autorizacion || null, - observacion || null, - cupCodigo, + ` + INSERT INTO autorizacion_version + (numero_autorizacion, version, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); + `, + [ + nuevaAutorizacion.numero_autorizacion, + nuevaAutorizacion.version || 1, + id_ips, + numero_documento_autorizante, + solicitanteId, + nuevaAutorizacion.fecha_autorizacion || null, + observacion || null, + cupCodigo, + cie10Codigo, + cie10Descripcion, tipoAutorizacion, - tipoServicio, - ] - ); + tipoServicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntregaFinal, + ] + ); await client.query('COMMIT'); @@ -2882,8 +3598,13 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar observacion, fecha_autorizacion, cup_codigo, + cie10_codigo, + cie10_descripcion, tipo_autorizacion, tipo_servicio, + ambito_atencion, + numero_orden, + estado_entrega, } = req.body; if (!id_ips || !numero_documento_autorizante) { @@ -2897,6 +3618,14 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar return res.status(400).json({ error: 'cup_codigo es obligatorio' }); } + 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() .toLowerCase(); @@ -2937,6 +3666,47 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar const actual = actualRes.rows[0]; const versionActual = Number(actual.version) || 1; + const cie10Codigo = cie10CodigoInput || actual.cie10_codigo || ''; + const cie10Descripcion = cie10DescripcionInput || actual.cie10_descripcion || ''; + const ambitoAtencion = ambitoInput || actual.ambito_atencion || ''; + + if (!cie10Codigo) { + await client.query('ROLLBACK'); + return res.status(400).json({ error: 'cie10_codigo es obligatorio' }); + } + if (!cie10Descripcion) { + await client.query('ROLLBACK'); + return res.status(400).json({ error: 'cie10_descripcion es obligatorio' }); + } + if (!ambitoAtencion) { + await client.query('ROLLBACK'); + return res.status(400).json({ error: 'ambito_atencion es obligatorio' }); + } + if (!ambitosPermitidos.includes(ambitoAtencion)) { + await client.query('ROLLBACK'); + return res.status(400).json({ error: 'ambito_atencion invalido' }); + } + + const numeroOrdenInput = + numero_orden === undefined ? null : String(numero_orden).trim(); + const numeroOrdenFinal = + ambitoAtencion === 'intramural' + ? (numeroOrdenInput !== null + ? (numeroOrdenInput || null) + : actual.numero_orden || null) + : null; + + const estadoEntregaInput = + estado_entrega === undefined ? '' : String(estado_entrega); + const estadoEntregaParsed = estadoEntregaInput + ? parseEstadoEntrega(estadoEntregaInput) + : ''; + if (estadoEntregaInput && !estadoEntregaParsed) { + await client.query('ROLLBACK'); + return res.status(400).json({ error: 'estado_entrega invalido' }); + } + const estadoEntregaFinal = + estadoEntregaParsed || actual.estado_entrega || 'pendiente_entrega'; const existeVersion = await client.query( 'SELECT 1 FROM autorizacion_version WHERE numero_autorizacion = $1 AND version = $2', @@ -2944,24 +3714,30 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar ); if (existeVersion.rows.length === 0) { - await client.query( - ` - INSERT INTO autorizacion_version - (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); - `, - [ - numero_autorizacion, - versionActual, - actual.id_ips, - actual.numero_documento_autorizante, - actual.fecha_autorizacion || null, - actual.observacion || null, - actual.cup_codigo || null, + await client.query( + ` + INSERT INTO autorizacion_version + (numero_autorizacion, version, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); + `, + [ + numero_autorizacion, + versionActual, + actual.id_ips, + actual.numero_documento_autorizante, + actual.id_usuario_solicitante || null, + actual.fecha_autorizacion || null, + actual.observacion || null, + actual.cup_codigo || null, + actual.cie10_codigo || null, + actual.cie10_descripcion || null, actual.tipo_autorizacion || null, - actual.tipo_servicio || null, - ] - ); + actual.tipo_servicio || null, + actual.ambito_atencion || null, + actual.numero_orden || null, + actual.estado_entrega || 'pendiente_entrega', + ] + ); } const nuevaVersion = versionActual + 1; @@ -2975,11 +3751,16 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar fecha_autorizacion = COALESCE($3::date, fecha_autorizacion), observacion = $4, cup_codigo = $5, - tipo_autorizacion = $6, - tipo_servicio = $7, - version = $8 - WHERE numero_autorizacion = $9 - RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, tipo_autorizacion, tipo_servicio, version, estado_autorizacion; + cie10_codigo = $6, + cie10_descripcion = $7, + tipo_autorizacion = $8, + tipo_servicio = $9, + ambito_atencion = $10, + numero_orden = $11, + estado_entrega = $12, + version = $13 + WHERE numero_autorizacion = $14 + RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega, version, estado_autorizacion; `, [ id_ips, @@ -2987,8 +3768,13 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar fechaAut, observacion || null, cupCodigo, + cie10Codigo, + cie10Descripcion, tipoAutorizacion, tipoServicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntregaFinal, nuevaVersion, numero_autorizacion, ] @@ -2996,22 +3782,28 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar const actualizado = updateRes.rows[0]; - await client.query( - ` - INSERT INTO autorizacion_version - (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9); - `, - [ - numero_autorizacion, - nuevaVersion, - id_ips, - numero_documento_autorizante, - actualizado.fecha_autorizacion || null, - observacion || null, - cupCodigo, + await client.query( + ` + INSERT INTO autorizacion_version + (numero_autorizacion, version, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15); + `, + [ + numero_autorizacion, + nuevaVersion, + id_ips, + numero_documento_autorizante, + actual.id_usuario_solicitante || null, + actualizado.fecha_autorizacion || null, + observacion || null, + cupCodigo, + cie10Codigo, + cie10Descripcion, tipoAutorizacion, tipoServicio, + ambitoAtencion, + numeroOrdenFinal, + estadoEntregaFinal, ] ); @@ -3084,6 +3876,100 @@ app.patch( } ); + /** + * PATCH /api/autorizaciones/:numero_autorizacion/estado-entrega + * Body: { estado_entrega } + * Administrador o administrativo_sede + */ +app.patch( + '/api/autorizaciones/:numero_autorizacion/estado-entrega', + verificarToken, + puedeActualizarEstadoEntrega, + async (req, res) => { + const { numero_autorizacion } = req.params; + const estado = String(req.body?.estado_entrega || '') + .trim() + .toLowerCase(); + + if (!ESTADOS_ENTREGA.includes(estado)) { + return res.status(400).json({ error: 'estado_entrega invalido' }); + } + + try { + const { rows } = await pool.query( + ` + UPDATE autorizacion + SET estado_entrega = $1 + WHERE numero_autorizacion = $2 + RETURNING numero_autorizacion, estado_entrega; + `, + [estado, numero_autorizacion] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: 'Autorizacion no encontrada' }); + } + + return res.json({ + mensaje: 'Estado de entrega actualizado', + autorizacion: rows[0], + }); + } catch (error) { + console.error('Error actualizando estado de entrega:', error.message); + return res.status(500).json({ error: 'Error actualizando estado de entrega' }); + } + } +); + +/** + * PATCH /api/autorizaciones/:numero_autorizacion/correo-inpec + * Body: { correo_inpec_pendiente, correo_inpec_enviado, correo_inpec_respuesta } + * Solo administrador + */ +app.patch( + '/api/autorizaciones/:numero_autorizacion/correo-inpec', + verificarToken, + esAdministrador, + async (req, res) => { + const { numero_autorizacion } = req.params; + const pendiente = req.body?.correo_inpec_pendiente; + const enviado = req.body?.correo_inpec_enviado; + const respuesta = req.body?.correo_inpec_respuesta; + + if (typeof pendiente !== 'boolean' || typeof enviado !== 'boolean' || typeof respuesta !== 'boolean') { + return res.status(400).json({ + error: 'correo_inpec_pendiente, correo_inpec_enviado y correo_inpec_respuesta deben ser booleanos', + }); + } + + try { + const { rows } = await pool.query( + ` + UPDATE autorizacion + SET correo_inpec_pendiente = $1, + correo_inpec_enviado = $2, + correo_inpec_respuesta = $3 + WHERE numero_autorizacion = $4 + RETURNING numero_autorizacion, correo_inpec_pendiente, correo_inpec_enviado, correo_inpec_respuesta; + `, + [pendiente, enviado, respuesta, numero_autorizacion] + ); + + if (rows.length === 0) { + return res.status(404).json({ error: 'Autorizacion no encontrada' }); + } + + return res.json({ + mensaje: 'Estado de correo INPEC actualizado', + autorizacion: rows[0], + }); + } catch (error) { + console.error('Error actualizando correo INPEC:', error.message); + return res.status(500).json({ error: 'Error actualizando correo INPEC' }); + } + } +); + /** * GET /api/autorizaciones?interno=1007362 * Devuelve todas las autorizaciones del interno. @@ -3103,31 +3989,39 @@ app.get('/api/autorizaciones', verificarToken, async (req, res) => { a.fecha_autorizacion, a.observacion, a.cup_codigo, + a.cie10_codigo, + a.cie10_descripcion, a.tipo_autorizacion, a.tipo_servicio, a.version, COALESCE(a.estado_autorizacion, 'pendiente') AS estado_autorizacion, + a.ambito_atencion, + a.numero_orden, + a.estado_entrega, a.id_ips, a.numero_documento_autorizante, ips.nombre_ips, - COALESCE(ips.municipio, e.epc_ciudad) AS municipio, - COALESCE(ips.departamento, e.epc_departamento) AS departamento, - COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio, - aut.nombre AS nombre_autorizante, - cc.descripcion AS cup_descripcion, - cc.nivel AS cup_nivel, - cc.especialidad AS cup_especialidad - FROM autorizacion a + COALESCE(ips.municipio, e.epc_ciudad) AS municipio, + COALESCE(ips.departamento, e.epc_departamento) AS departamento, + COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio, + aut.nombre AS nombre_autorizante, + u.nombre_completo AS nombre_solicitante, + cc.descripcion AS cup_descripcion, + cc.nivel AS cup_nivel, + cc.especialidad AS cup_especialidad + FROM autorizacion a JOIN ips ON a.id_ips = ips.id_ips LEFT JOIN ingreso i ON a.interno = i.interno - LEFT JOIN establecimiento e - ON i.codigo_establecimiento = e.codigo_establecimiento - JOIN autorizante aut - ON a.numero_documento_autorizante = aut.numero_documento - LEFT JOIN cups_cubiertos cc - ON a.cup_codigo = cc.codigo + LEFT JOIN establecimiento e + ON i.codigo_establecimiento = e.codigo_establecimiento + JOIN autorizante aut + ON a.numero_documento_autorizante = aut.numero_documento + LEFT JOIN usuario u + ON a.id_usuario_solicitante = u.id_usuario + LEFT JOIN cups_cubiertos cc + ON a.cup_codigo = cc.codigo WHERE a.interno = $1 ${esAdmin ? '' : "AND COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')"} ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC; @@ -3155,6 +4049,9 @@ app.get('/api/generar-excel-autorizaciones', async (req, res) => { { header: 'Interno', key: 'interno', width: 15 }, { header: 'Nombre del paciente', key: 'nombre_paciente', width: 30 }, { header: 'CUPS', key: 'cup_codigo', width: 12 }, + { header: 'CIE-10', key: 'cie10_codigo', width: 12 }, + { header: 'Diagnostico', key: 'cie10_descripcion', width: 35 }, + { header: 'Ambito', key: 'ambito_atencion', width: 14 }, { header: 'Nivel', key: 'cup_nivel', width: 10 }, { header: 'Tipo autorizacion', key: 'tipo_autorizacion', width: 20 }, { header: 'Tipo servicio', key: 'tipo_servicio', width: 18 }, @@ -3173,6 +4070,9 @@ app.get('/api/generar-excel-autorizaciones', async (req, res) => { p.primer_nombre || ' ' || p.segundo_nombre || ' ' || p.primer_apellido || ' ' || p.segundo_apellido AS nombre_paciente, i.nombre_ips, a.cup_codigo, + a.cie10_codigo, + a.cie10_descripcion, + a.ambito_atencion, cc.nivel AS cup_nivel, a.tipo_autorizacion, a.tipo_servicio, @@ -3201,6 +4101,9 @@ app.get('/api/generar-excel-autorizaciones', async (req, res) => { interno: row.interno, nombre_paciente: row.nombre_paciente, cup_codigo: row.cup_codigo, + cie10_codigo: row.cie10_codigo, + cie10_descripcion: row.cie10_descripcion, + ambito_atencion: row.ambito_atencion, cup_nivel: row.cup_nivel, tipo_autorizacion: row.tipo_autorizacion, tipo_servicio: tipoServicio, @@ -3692,6 +4595,9 @@ app.get('/api/roles', verificarToken, esAdministrador, async (req, res) => { */ app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => { const { fecha_inicio, fecha_fin } = req.query; + const establecimiento = String(req.query.establecimiento || '').trim(); + const ambito = String(req.query.ambito || '').trim().toLowerCase(); + const ips = String(req.query.ips || '').trim(); if (!fecha_inicio || !fecha_fin) { return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' }); @@ -3701,6 +4607,37 @@ app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => { const esAdmin = req.usuario?.nombre_rol === 'administrador'; const limit = Math.min(Number(req.query.limit) || 500, 2000); const offset = Math.max(Number(req.query.offset) || 0, 0); + const ambitosPermitidos = ['intramural', 'extramural']; + if (ambito && !ambitosPermitidos.includes(ambito)) { + return res.status(400).json({ error: 'ambito invalido' }); + } + + const whereParts = ['a.fecha_autorizacion BETWEEN $1 AND $2']; + const params = [fecha_inicio, fecha_fin]; + + if (!esAdmin) { + whereParts.push("COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')"); + } + + if (establecimiento) { + params.push(`%${establecimiento}%`); + whereParts.push(`(e.codigo_establecimiento ILIKE $${params.length} OR e.nombre_establecimiento ILIKE $${params.length})`); + } + + if (ambito) { + params.push(ambito); + whereParts.push(`a.ambito_atencion = $${params.length}`); + } + + if (ips) { + params.push(`%${ips}%`); + whereParts.push( + `(ips.nombre_ips ILIKE $${params.length} OR ips.nit ILIKE $${params.length} OR ips.codigo_ips ILIKE $${params.length})` + ); + } + + params.push(limit); + params.push(offset); const sql = ` SELECT @@ -3708,37 +4645,44 @@ app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => { a.fecha_autorizacion, a.observacion, a.cup_codigo, + a.cie10_codigo, + a.cie10_descripcion, a.tipo_autorizacion, a.tipo_servicio, a.version, COALESCE(a.estado_autorizacion, 'pendiente') AS estado_autorizacion, + a.ambito_atencion, + a.numero_orden, + a.estado_entrega, p.interno, p.primer_nombre || ' ' || COALESCE(p.segundo_nombre, '') || ' ' || p.primer_apellido || ' ' || COALESCE(p.segundo_apellido, '') AS nombre_paciente, ips.nombre_ips, - COALESCE(ips.municipio, e.epc_ciudad) AS municipio, - COALESCE(ips.departamento, e.epc_departamento) AS departamento, - COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio, - aut.nombre AS nombre_autorizante, - e.nombre_establecimiento, - CASE WHEN cc.codigo IS NULL THEN false ELSE true END AS cup_cubierto, + COALESCE(ips.municipio, e.epc_ciudad) AS municipio, + COALESCE(ips.departamento, e.epc_departamento) AS departamento, + COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio, + aut.nombre AS nombre_autorizante, + u.nombre_completo AS nombre_solicitante, + e.codigo_establecimiento, + e.nombre_establecimiento, + CASE WHEN cc.codigo IS NULL THEN false ELSE true END AS cup_cubierto, cc.descripcion AS cup_descripcion, cc.nivel AS cup_nivel, cc.especialidad AS cup_especialidad FROM autorizacion a JOIN paciente p ON a.interno = p.interno - JOIN ips ON a.id_ips = ips.id_ips - JOIN autorizante aut ON a.numero_documento_autorizante = aut.numero_documento - JOIN ingreso i ON a.interno = i.interno - JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento - LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo - WHERE a.fecha_autorizacion BETWEEN $1 AND $2 - ${esAdmin ? '' : "AND COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')"} + JOIN ips ON a.id_ips = ips.id_ips + JOIN autorizante aut ON a.numero_documento_autorizante = aut.numero_documento + LEFT JOIN usuario u ON a.id_usuario_solicitante = u.id_usuario + JOIN ingreso i ON a.interno = i.interno + JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento + LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo + WHERE ${whereParts.join('\n AND ')} ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC - LIMIT $3 OFFSET $4 + LIMIT $${params.length - 1} OFFSET $${params.length} `; - const { rows } = await pool.query(sql, [fecha_inicio, fecha_fin, limit, offset]); + const { rows } = await pool.query(sql, params); res.json(rows); } catch (error) { @@ -3870,9 +4814,9 @@ app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador, * GET /api/autorizaciones-por-fecha/zip * Query params: fecha_inicio, fecha_fin * Devuelve un ZIP con todos los PDFs de las autorizaciones de ese rango - * Solo para administradores + * Administrador o administrativo_sede (solo autorizadas) */ -app.get('/api/autorizaciones-por-fecha/zip', verificarToken, esAdministrador, crearJobZipHandler); +app.get('/api/autorizaciones-por-fecha/zip', verificarToken, puedeDescargarZipAutorizaciones, crearJobZipHandler); ensureCupsTables().catch((error) => { console.error('Error inicializando tablas CUPS:', error.message); @@ -3882,6 +4826,10 @@ ensureAutorizacionVersionTables().catch((error) => { console.error('Error inicializando tablas de versiones:', error.message); }); +ensureAutorizacionExtras().catch((error) => { + console.error('Error inicializando columnas extra de autorizaciones:', error.message); +}); + ensureIpsConvenio().catch((error) => { console.error('Error inicializando convenio de IPS:', error.message); }); diff --git a/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.css b/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.css index 2fcd612..72cc828 100644 --- a/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.css +++ b/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.css @@ -87,7 +87,8 @@ font-size: 0.9rem; } -.form-group input { +.form-group input, +.form-group select { width: 100%; padding: 12px 16px; border: 2px solid var(--color-input-border); @@ -98,7 +99,8 @@ color: var(--color-text-main); } -.form-group input:focus { +.form-group input:focus, +.form-group select:focus { outline: none; border-color: #1976d2; box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1); @@ -284,6 +286,12 @@ font-family: "Courier New", monospace; } +.numero-orden { + font-weight: 600; + font-family: "Courier New", monospace; + text-align: center; +} + .cup-codigo { font-weight: 600; font-family: "Courier New", monospace; @@ -321,8 +329,10 @@ .nombre-paciente, .ips, .autorizante, +.solicitante, .establecimiento, -.estado { +.estado, +.estado-entrega { max-width: 180px; line-height: 1.3; } @@ -340,7 +350,8 @@ text-transform: uppercase; } -.estado select { +.estado select, +.estado-entrega select { width: 100%; min-width: 140px; padding: 10px 12px; @@ -500,8 +511,10 @@ .nombre-paciente, .ips, .autorizante, + .solicitante, .establecimiento, - .estado { + .estado, + .estado-entrega { max-width: 120px; } } diff --git a/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.html b/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.html index 797645b..cfbb64e 100644 --- a/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.html +++ b/saludut-inpec/src/app/components/autorizaciones-por-fecha/autorizaciones-por-fecha.html @@ -74,6 +74,37 @@ + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
@@ -97,8 +128,8 @@ placeholder="Numero de autorizacion" /> -
-
+
+
+ + + + + + + + Guardando... + + + + + {{ getEstadoEntregaLabel(aut.estado_entrega) }} + + +
+
+ +
+ + + {{ archivoHistorialClinico.name }} + +
+
+ +
+ +
+ + + {{ archivoAnexo.name }} + +
+
+ +
+ +
+ + Leyendo PDF... +
+
+ +
+ {{ autorrellenoInfo }} +
+
+ {{ autorrellenoError }} +
+ +
+ + +
+ +
+ + +
+
@@ -285,6 +373,25 @@ {{ errorCups }}
+
+ + +
+ +
+ + +
+
+
+ Subiendo archivos hospitalarios... +
+
{{ mensajeAutorizacion }}
@@ -356,6 +467,7 @@ Nivel Tipo autorizacion Tipo servicio + Ambito Version IPS Autoriza @@ -371,6 +483,7 @@ {{ a.cup_nivel }} {{ getTipoAutorizacionLabel(a.tipo_autorizacion) }} {{ getTipoServicioLabel(a.tipo_servicio) }} + {{ getAmbitoLabel(a.ambito_atencion) }}
v{{ a.version || 1 }} diff --git a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts index 8e3f74e..724331e 100644 --- a/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts +++ b/saludut-inpec/src/app/components/autorizaciones/autorizaciones.ts @@ -4,7 +4,8 @@ import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { AuthService } from '../../services/auth'; import { PacienteService, AutorizacionVersion, CupInfo } from '../../services/paciente'; -import { finalize } from 'rxjs/operators'; +import { finalize, switchMap, catchError, map } from 'rxjs/operators'; +import { of } from 'rxjs'; import { AppHeaderComponent } from '../shared/app-header/app-header'; import { JobsService } from '../../services/jobs'; @@ -51,8 +52,12 @@ export class AutorizacionesComponent { fecha_autorizacion: '', observacion: '', cup_codigo: '', + cie10_codigo: '', + cie10_descripcion: '', tipo_autorizacion: 'consultas_externas', tipo_servicio: '', + ambito_atencion: '', + numero_orden: '', }; guardandoAutorizacion = false; @@ -64,6 +69,12 @@ export class AutorizacionesComponent { errorAutLista: string | null = null; descargandoPdf = false; + archivoHistorialClinico: File | null = null; + archivoAnexo: File | null = null; + subiendoArchivosHospitalarios = false; + autorrellenandoPdf = false; + autorrellenoInfo: string | null = null; + autorrellenoError: string | null = null; constructor( private authService: AuthService, @@ -137,8 +148,12 @@ export class AutorizacionesComponent { fecha_autorizacion: '', observacion: '', cup_codigo: '', + cie10_codigo: '', + cie10_descripcion: '', tipo_autorizacion: 'consultas_externas', tipo_servicio: '', + ambito_atencion: '', + numero_orden: '', }; this.autorizacionesPaciente = []; @@ -153,6 +168,7 @@ export class AutorizacionesComponent { this.cargarIps(p.interno, this.verMasIps); this.cargarAutorizantes(); + this.limpiarArchivosHospitalarios(); } cerrarAutorizacion(): void { @@ -172,6 +188,7 @@ export class AutorizacionesComponent { this.versionActualPorAutorizacion = {}; this.versionSeleccionada = {}; this.cargandoVersiones = {}; + this.limpiarArchivosHospitalarios(); this.cdr.markForCheck(); } @@ -320,9 +337,174 @@ export class AutorizacionesComponent { 'brigadas_ambulancias_hospitalarios' ) { this.formAutorizacion.tipo_servicio = ''; + this.limpiarArchivosHospitalarios(); } } + onTipoServicioChange(): void { + const servicio = String(this.formAutorizacion.tipo_servicio || '') + .trim() + .toLowerCase(); + if (servicio !== 'hospitalarios') { + this.limpiarArchivosHospitalarios(); + } + } + + onAmbitoChange(): void { + const ambito = String(this.formAutorizacion.ambito_atencion || '') + .trim() + .toLowerCase(); + if (ambito !== 'intramural') { + this.formAutorizacion.numero_orden = ''; + } + } + + onHistorialClinicoChange(event: Event): void { + this.setPdfFile(event, 'historial'); + } + + onAnexoChange(event: Event): void { + this.setPdfFile(event, 'anexo'); + } + + autorrellenarDesdePdf(): void { + const archivo = this.archivoAnexo || this.archivoHistorialClinico; + if (!archivo) { + this.autorrellenoError = 'Adjunta un PDF para autorrellenar.'; + return; + } + + this.autorrellenoInfo = null; + this.autorrellenoError = null; + this.autorrellenandoPdf = true; + + this.pacienteService + .autorrellenarAutorizacionPdf(archivo) + .pipe( + finalize(() => { + this.autorrellenandoPdf = false; + this.cdr.markForCheck(); + }) + ) + .subscribe({ + next: (resp: any) => { + if (resp?.cup_codigo) { + this.formAutorizacion.cup_codigo = resp.cup_codigo; + this.buscarCups(); + } + if (resp?.cie10_codigo) { + this.formAutorizacion.cie10_codigo = resp.cie10_codigo; + } + if (resp?.cie10_descripcion) { + this.formAutorizacion.cie10_descripcion = resp.cie10_descripcion; + } + + const infoParts = []; + if (resp?.nombre_paciente) { + infoParts.push(`Paciente: ${resp.nombre_paciente}`); + } + if (resp?.numero_documento) { + infoParts.push(`Documento: ${resp.numero_documento}`); + } + if (resp?.formato) { + infoParts.push(`Formato: ${this.getFormatoPdfLabel(resp.formato)}`); + } + if (resp?.ocr_usado) { + infoParts.push('OCR usado'); + } + const warnings = Array.isArray(resp?.warnings) ? resp.warnings : []; + if (warnings.length) { + infoParts.push(`Avisos: ${warnings.join(', ')}`); + } + + this.autorrellenoInfo = infoParts.join(' | ') || 'PDF procesado.'; + + const docPdf = String(resp?.numero_documento || '').trim(); + const docPaciente = String(this.pacienteSeleccionado?.numero_documento || '').trim(); + if (docPdf && docPaciente && docPdf !== docPaciente) { + this.autorrellenoError = + 'El documento del PDF no coincide con el paciente seleccionado.'; + } + + if (!this.autorrellenoError && warnings.includes('no_text_extracted')) { + if (resp?.ocr_disponible) { + this.autorrellenoError = + 'No se pudo leer texto del PDF. Revisa que el archivo sea legible.'; + } else { + this.autorrellenoError = + 'No se pudo leer texto del PDF. OCR no disponible en el servidor.'; + } + } + }, + error: (err) => { + console.error(err); + this.autorrellenoError = + err?.error?.error || 'Error leyendo datos del PDF.'; + }, + }); + } + + private setPdfFile(event: Event, tipo: 'historial' | 'anexo'): void { + const input = event.target as HTMLInputElement | null; + const file = input?.files?.[0] || null; + + if (!file) { + if (tipo === 'historial') { + this.archivoHistorialClinico = null; + } else { + this.archivoAnexo = null; + } + return; + } + + if (!this.isPdfFile(file)) { + if (input) { + input.value = ''; + } + this.errorAutorizacion = 'Solo se permiten archivos PDF.'; + if (tipo === 'historial') { + this.archivoHistorialClinico = null; + } else { + this.archivoAnexo = null; + } + return; + } + + if (tipo === 'historial') { + this.archivoHistorialClinico = file; + } else { + this.archivoAnexo = file; + } + } + + private isPdfFile(file: File): boolean { + const nombre = String(file.name || '').toLowerCase(); + const tipo = String(file.type || '').toLowerCase(); + return tipo === 'application/pdf' || nombre.endsWith('.pdf'); + } + + private getFormatoPdfLabel(formato: string): string { + if (formato === 'ANEXO_TECNICO') { + return 'Anexo tecnico'; + } + if (formato === 'ANEXO_URGENCIAS') { + return 'Anexo urgencias'; + } + return 'Desconocido'; + } + + private limpiarArchivosHospitalarios(): void { + this.archivoHistorialClinico = null; + this.archivoAnexo = null; + this.resetAutorrelleno(); + } + + private resetAutorrelleno(): void { + this.autorrellenandoPdf = false; + this.autorrellenoInfo = null; + this.autorrellenoError = null; + } + getTipoAutorizacionLabel(tipo: string | null | undefined): string { const normalizado = String(tipo || '').toLowerCase(); if (normalizado === 'consultas_externas') { @@ -348,6 +530,17 @@ export class AutorizacionesComponent { return tipo ? String(tipo) : ''; } + getAmbitoLabel(ambito: string | null | undefined): string { + const normalizado = String(ambito || '').toLowerCase(); + if (normalizado === 'intramural') { + return 'Intramural'; + } + if (normalizado === 'extramural') { + return 'Extramural'; + } + return ambito ? String(ambito) : ''; + } + getEstadoAutorizacionLabel(estado: string | null | undefined): string { const normalizado = String(estado || 'pendiente').toLowerCase(); if (normalizado === 'autorizado') { @@ -454,10 +647,15 @@ export class AutorizacionesComponent { fecha_autorizacion: this.formatDateInput(autorizacion.fecha_autorizacion), observacion: autorizacion.observacion || '', cup_codigo: autorizacion.cup_codigo || '', + cie10_codigo: autorizacion.cie10_codigo || '', + cie10_descripcion: autorizacion.cie10_descripcion || '', tipo_autorizacion: autorizacion.tipo_autorizacion || 'consultas_externas', tipo_servicio: autorizacion.tipo_servicio || '', + ambito_atencion: autorizacion.ambito_atencion || '', + numero_orden: autorizacion.numero_orden || '', }; this.cupSeleccionado = null; + this.limpiarArchivosHospitalarios(); this.onTipoAutorizacionChange(); this.onIpsChange(); @@ -476,9 +674,14 @@ export class AutorizacionesComponent { fecha_autorizacion: '', observacion: '', cup_codigo: '', + cie10_codigo: '', + cie10_descripcion: '', tipo_autorizacion: 'consultas_externas', tipo_servicio: '', + ambito_atencion: '', + numero_orden: '', }; + this.limpiarArchivosHospitalarios(); } guardarAutorizacion(): void { @@ -501,6 +704,26 @@ export class AutorizacionesComponent { this.errorAutorizacion = 'Debe seleccionar un CUPS.'; return; } + if (!this.formAutorizacion.cie10_codigo) { + this.errorAutorizacion = 'Debe ingresar el codigo CIE-10.'; + return; + } + if (!this.formAutorizacion.cie10_descripcion) { + this.errorAutorizacion = 'Debe ingresar el diagnostico.'; + return; + } + if (!this.formAutorizacion.ambito_atencion) { + this.errorAutorizacion = 'Debe seleccionar el ambito (intramural o extramural).'; + return; + } + const ambitoSeleccionado = String(this.formAutorizacion.ambito_atencion || '') + .trim() + .toLowerCase(); + const numeroOrdenInput = String(this.formAutorizacion.numero_orden || '').trim(); + if (ambitoSeleccionado === 'intramural' && !numeroOrdenInput) { + this.errorAutorizacion = 'Debe ingresar el numero de orden para intramural.'; + return; + } const tipoAutorizacion = String( this.formAutorizacion.tipo_autorizacion || 'consultas_externas' ).toLowerCase(); @@ -515,6 +738,21 @@ export class AutorizacionesComponent { return; } + const esHospitalario = + requiereServicio && String(tipoServicio).toLowerCase() === 'hospitalarios'; + const requiereAdjuntos = esHospitalario && !this.autorizacionEditando; + const tieneAdjuntos = + !!this.archivoHistorialClinico || !!this.archivoAnexo; + + if ( + (requiereAdjuntos || tieneAdjuntos) && + (!this.archivoHistorialClinico || !this.archivoAnexo) + ) { + this.errorAutorizacion = + 'Debe adjuntar historial clinico (HC) y anexo en hospitalarios.'; + return; + } + const payload = { interno: this.pacienteSeleccionado.interno, id_ips: Number(this.formAutorizacion.id_ips), @@ -525,8 +763,15 @@ export class AutorizacionesComponent { fecha_autorizacion: this.formAutorizacion.fecha_autorizacion || undefined, cup_codigo: this.formAutorizacion.cup_codigo, + cie10_codigo: this.formAutorizacion.cie10_codigo, + cie10_descripcion: this.formAutorizacion.cie10_descripcion, tipo_autorizacion: tipoAutorizacion, tipo_servicio: requiereServicio ? tipoServicio : undefined, + ambito_atencion: this.formAutorizacion.ambito_atencion, + numero_orden: + ambitoSeleccionado === 'intramural' && numeroOrdenInput + ? numeroOrdenInput + : undefined, }; this.guardandoAutorizacion = true; @@ -540,13 +785,36 @@ export class AutorizacionesComponent { request$ .pipe( + switchMap((resp: any) => { + const numeroAutorizacion = resp?.numero_autorizacion; + if ( + esHospitalario && + this.archivoHistorialClinico && + this.archivoAnexo && + numeroAutorizacion + ) { + this.subiendoArchivosHospitalarios = true; + return this.pacienteService + .subirArchivosHospitalarios( + numeroAutorizacion, + this.archivoHistorialClinico, + this.archivoAnexo + ) + .pipe( + map(() => ({ resp, archivosOk: true, error: null })), + catchError((error) => of({ resp, archivosOk: false, error })) + ); + } + return of({ resp, archivosOk: true, error: null }); + }), finalize(() => { this.guardandoAutorizacion = false; + this.subiendoArchivosHospitalarios = false; this.cdr.markForCheck(); }) ) .subscribe({ - next: (resp: any) => { + next: ({ resp, archivosOk, error }) => { if (this.autorizacionEditando) { const versionTexto = resp?.version ? ` (v${resp.version})` : ''; this.mensajeAutorizacion = `Autorizacion ${resp.numero_autorizacion} actualizada${versionTexto}.`; @@ -555,6 +823,16 @@ export class AutorizacionesComponent { } else { this.mensajeAutorizacion = `Autorizacion N ${resp.numero_autorizacion} creada el ${resp.fecha_autorizacion}.`; } + + if (esHospitalario && this.archivoHistorialClinico && this.archivoAnexo) { + if (archivosOk) { + this.limpiarArchivosHospitalarios(); + } else { + this.errorAutorizacion = + error?.error?.error || + 'Autorizacion guardada, pero fallo la carga de archivos hospitalarios.'; + } + } }, error: (err) => { console.error(err); diff --git a/saludut-inpec/src/app/components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas.html b/saludut-inpec/src/app/components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas.html index ce26cd8..19a887a 100644 --- a/saludut-inpec/src/app/components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas.html +++ b/saludut-inpec/src/app/components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas.html @@ -76,6 +76,14 @@ Sin IPS {{ resumen.sin_ips || 0 }}
+
+ Sin diagnostico + {{ resumen.sin_diagnostico || 0 }} +
+
+ Sin ambito + {{ resumen.sin_ambito || 0 }} +
CUPS no cubiertos {{ resumen.cups_no_cubiertos || 0 }} diff --git a/saludut-inpec/src/app/services/auth.ts b/saludut-inpec/src/app/services/auth.ts index 5108ab1..db7b3a5 100644 --- a/saludut-inpec/src/app/services/auth.ts +++ b/saludut-inpec/src/app/services/auth.ts @@ -313,10 +313,22 @@ export class AuthService { fechaInicio: string, fechaFin: string, limit = 500, - offset = 0 + offset = 0, + establecimiento?: string, + ambito?: string, + ips?: string ): Observable { const headers = this.getAuthHeaders(); - const params = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset }; + const params: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset }; + if (establecimiento) { + params.establecimiento = establecimiento; + } + if (ambito) { + params.ambito = ambito; + } + if (ips) { + params.ips = ips; + } return this.http.get(`${this.API_URL}/autorizaciones-por-fecha`, { headers, params }); } @@ -356,11 +368,27 @@ export class AuthService { ); } - crearJobZipAutorizaciones(fechaInicio: string, fechaFin: string): Observable { + crearJobZipAutorizaciones( + fechaInicio: string, + fechaFin: string, + establecimiento?: string, + ambito?: string, + ips?: string + ): Observable { const headers = this.getAuthHeaders(); + const payload: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin }; + if (establecimiento) { + payload.establecimiento = establecimiento; + } + if (ambito) { + payload.ambito = ambito; + } + if (ips) { + payload.ips = ips; + } return this.http.post( `${this.API_URL}/jobs/autorizaciones-zip`, - { fecha_inicio: fechaInicio, fecha_fin: fechaFin }, + payload, { headers } ); } diff --git a/saludut-inpec/src/app/services/job-types.ts b/saludut-inpec/src/app/services/job-types.ts index ddffb9d..c7b7474 100644 --- a/saludut-inpec/src/app/services/job-types.ts +++ b/saludut-inpec/src/app/services/job-types.ts @@ -25,6 +25,8 @@ export interface JobResult { duplicados?: number | null; creadas?: number | null; sin_paciente?: number | null; + sin_diagnostico?: number | null; + sin_ambito?: number | null; sin_cups?: number | null; sin_ips?: number | null; cups_no_cubiertos?: number | null; diff --git a/saludut-inpec/src/app/services/paciente.ts b/saludut-inpec/src/app/services/paciente.ts index 2966e62..a080e74 100644 --- a/saludut-inpec/src/app/services/paciente.ts +++ b/saludut-inpec/src/app/services/paciente.ts @@ -67,8 +67,13 @@ export interface CrearAutorizacionPayload { id_ips: number; numero_documento_autorizante: number; cup_codigo: string; + cie10_codigo?: string; + cie10_descripcion?: string; tipo_autorizacion?: string; tipo_servicio?: string; + ambito_atencion?: string; + numero_orden?: string; + estado_entrega?: string; observacion?: string; fecha_autorizacion?: string; // yyyy-MM-dd } @@ -78,6 +83,11 @@ export interface RespuestaAutorizacion { fecha_autorizacion: string; version?: number; estado_autorizacion?: string; + cie10_codigo?: string | null; + cie10_descripcion?: string | null; + ambito_atencion?: string | null; + numero_orden?: string | null; + estado_entrega?: string | null; } export interface AutorizacionListado { @@ -85,6 +95,8 @@ export interface AutorizacionListado { fecha_autorizacion: string; observacion: string | null; cup_codigo?: string | null; + cie10_codigo?: string | null; + cie10_descripcion?: string | null; cup_descripcion?: string | null; cup_nivel?: string | null; cup_especialidad?: string | null; @@ -92,6 +104,9 @@ export interface AutorizacionListado { tipo_servicio?: string | null; version?: number | null; estado_autorizacion?: string | null; + ambito_atencion?: string | null; + numero_orden?: string | null; + estado_entrega?: string | null; id_ips?: number | null; numero_documento_autorizante?: number | null; nombre_ips: string; @@ -99,6 +114,7 @@ export interface AutorizacionListado { departamento?: string | null; ips_tiene_convenio?: boolean | null; nombre_autorizante: string; + nombre_solicitante?: string | null; } export interface AutorizacionVersion { @@ -229,6 +245,41 @@ export class PacienteService { ); } + subirArchivosHospitalarios( + numeroAutorizacion: string, + historialClinico: File, + anexo: File + ): Observable { + const formData = new FormData(); + formData.append('historial_clinico', historialClinico); + formData.append('anexo', anexo); + + const token = this.authService.getToken(); + const headers = new HttpHeaders({ + Authorization: `Bearer ${token}`, + }); + + return this.http.post( + `${this.API_URL}/autorizaciones/${numeroAutorizacion}/archivos`, + formData, + { headers } + ); + } + + autorrellenarAutorizacionPdf(archivo: File): Observable { + const formData = new FormData(); + formData.append('archivo', archivo); + + const token = this.authService.getToken(); + const headers = new HttpHeaders({ + Authorization: `Bearer ${token}`, + }); + + return this.http.post(`${this.API_URL}/autorizaciones/autorrellenar`, formData, { + headers, + }); + } + obtenerVersionesAutorizacion(numeroAutorizacion: string): Observable { return this.http.get( `${this.API_URL}/autorizaciones/${numeroAutorizacion}/versiones`, @@ -345,6 +396,14 @@ export class PacienteService { ); } + actualizarEstadoEntrega(numeroAutorizacion: string, estado: string): Observable { + return this.http.patch( + `${this.API_URL}/autorizaciones/${numeroAutorizacion}/estado-entrega`, + { estado_entrega: estado }, + { headers: this.getAuthHeaders() } + ); + } + actualizarEstadoAutorizacionesMasivo( fechaInicio: string, fechaFin: string,