cambios nuevos
This commit is contained in:
parent
6ace09ec6c
commit
50e6aef612
@ -343,5 +343,3 @@ Reinicia el backend:
|
|||||||
rc-service saludut-backend restart
|
rc-service saludut-backend restart
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
BIN
backend/src/CIE-10.xlsx
Normal file
BIN
backend/src/CIE-10.xlsx
Normal file
Binary file not shown.
Binary file not shown.
@ -327,29 +327,66 @@ def extract_cups(lines, norm_lines):
|
|||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_cie10_code(code):
|
||||||
|
if not code:
|
||||||
|
return None
|
||||||
|
cleaned = re.sub(r"[\s\.]", "", code).upper()
|
||||||
|
cleaned = re.sub(r"[^A-Z0-9]", "", cleaned)
|
||||||
|
return cleaned or None
|
||||||
|
|
||||||
|
|
||||||
|
def clean_diagnosis_desc(desc):
|
||||||
|
if not desc:
|
||||||
|
return None
|
||||||
|
desc = re.sub(r"(?i)impresion diagnostica", "", desc)
|
||||||
|
desc = re.sub(
|
||||||
|
r"(?i)diagnostico(s)?( principal| de egreso| egreso| principal)?", "", desc
|
||||||
|
)
|
||||||
|
desc = re.sub(r"(?i)dx( principal| de egreso| egreso| secundaria| secundario)?", "", desc)
|
||||||
|
desc = re.sub(r"(?i)cie[-\s]*10", "", desc)
|
||||||
|
desc = desc.strip(" -:")
|
||||||
|
return desc or None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_cie10_codes_from_line(line):
|
||||||
|
if not line:
|
||||||
|
return []
|
||||||
|
matches = re.findall(
|
||||||
|
r"\b([A-Z]\s*\d{2,4}(?:\s*\.\s*\d{1,2})?[A-Z0-9]?)\b",
|
||||||
|
line,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
codes = []
|
||||||
|
for match in matches:
|
||||||
|
code = normalize_cie10_code(match)
|
||||||
|
if code and code not in codes:
|
||||||
|
codes.append(code)
|
||||||
|
return codes
|
||||||
|
|
||||||
|
|
||||||
def parse_cie10_from_line(line):
|
def parse_cie10_from_line(line):
|
||||||
if not line:
|
if not line:
|
||||||
return None, None
|
return None, None
|
||||||
match = re.search(r"\(([A-Z][0-9]{2,4}[A-Z0-9]?)\)", line, re.IGNORECASE)
|
match = re.search(
|
||||||
|
r"\(([A-Z][0-9]{2,4}(?:\.[0-9]{1,2})?[A-Z0-9]?)\)",
|
||||||
|
line,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
if match:
|
if match:
|
||||||
code = match.group(1).upper()
|
code = normalize_cie10_code(match.group(1))
|
||||||
desc = line[: match.start()].strip(" -:")
|
desc = clean_diagnosis_desc(line[: match.start()].strip(" -:"))
|
||||||
desc = re.sub(r"(?i)diagnostico principal", "", desc).strip()
|
|
||||||
desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip()
|
|
||||||
return code, desc.title() if desc else None
|
return code, desc.title() if desc else None
|
||||||
match = re.search(r"\b([A-Z])(?:\s*\d){2,4}[A-Z0-9]?\b", line, re.IGNORECASE)
|
match = re.search(
|
||||||
|
r"\b([A-Z]\s*\d{2,4}(?:\s*\.\s*\d{1,2})?[A-Z0-9]?)\b",
|
||||||
|
line,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
if match:
|
if match:
|
||||||
code = re.sub(r"\s+", "", match.group(0)).upper()
|
code = normalize_cie10_code(match.group(1))
|
||||||
desc = line[match.end():].strip(" -:")
|
desc = line[match.end():].strip(" -:")
|
||||||
if not desc:
|
if not desc:
|
||||||
desc = line[: match.start()].strip(" -:")
|
desc = line[: match.start()].strip(" -:")
|
||||||
desc = re.sub(r"(?i)diagnostico principal", "", desc).strip()
|
desc = clean_diagnosis_desc(desc)
|
||||||
desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip()
|
|
||||||
return code, desc.title() if desc else None
|
|
||||||
match = re.search(r"\b([A-Z][0-9]{2,4}[A-Z0-9]?)\b", line, re.IGNORECASE)
|
|
||||||
if match:
|
|
||||||
code = match.group(1).upper()
|
|
||||||
desc = line[match.end():].strip(" -:")
|
|
||||||
return code, desc.title() if desc else None
|
return code, desc.title() if desc else None
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
@ -390,20 +427,31 @@ def extract_cups_hint(lines, norm_lines):
|
|||||||
def extract_cie10_list(lines, norm_lines):
|
def extract_cie10_list(lines, norm_lines):
|
||||||
results = []
|
results = []
|
||||||
seen = set()
|
seen = set()
|
||||||
|
diag_tokens = [
|
||||||
|
"DIAGNOSTICO",
|
||||||
|
"DIAGNOSTICOS",
|
||||||
|
"IMPRESION DIAGNOSTICA",
|
||||||
|
"CIE10",
|
||||||
|
"CIE-10",
|
||||||
|
"DX",
|
||||||
|
"DX PRINCIPAL",
|
||||||
|
"DX EGRESO",
|
||||||
|
"DIAGNOSTICO PRINCIPAL",
|
||||||
|
"DIAGNOSTICO DE EGRESO",
|
||||||
|
"DIAGNOSTICO EGRESO",
|
||||||
|
]
|
||||||
for i, nline in enumerate(norm_lines):
|
for i, nline in enumerate(norm_lines):
|
||||||
if (
|
if not any(token in nline for token in diag_tokens):
|
||||||
"DIAGNOSTICO" in nline
|
continue
|
||||||
or "IMPRESION DIAGNOSTICA" in nline
|
for j in range(i, min(i + 12, len(lines))):
|
||||||
or "CIE10" in nline
|
|
||||||
):
|
|
||||||
for j in range(i, min(i + 6, len(lines))):
|
|
||||||
code, desc = parse_cie10_from_line(lines[j])
|
code, desc = parse_cie10_from_line(lines[j])
|
||||||
if not code:
|
if code and code not in seen:
|
||||||
continue
|
|
||||||
if code in seen:
|
|
||||||
continue
|
|
||||||
seen.add(code)
|
seen.add(code)
|
||||||
results.append((code, desc))
|
results.append((code, desc))
|
||||||
|
for extra_code in extract_cie10_codes_from_line(lines[j]):
|
||||||
|
if extra_code and extra_code not in seen:
|
||||||
|
seen.add(extra_code)
|
||||||
|
results.append((extra_code, None))
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|
||||||
@ -443,6 +491,37 @@ def extract_fecha_ingreso_urgencias(lines, norm_lines):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def extract_fecha_egreso(lines, norm_lines):
|
||||||
|
keys = [
|
||||||
|
"FECHA DE EGRESO",
|
||||||
|
"FECHA EGRESO",
|
||||||
|
"EGRESO",
|
||||||
|
"EGRESA",
|
||||||
|
"ALTA MEDICA",
|
||||||
|
"ALTA HOSPITALARIA",
|
||||||
|
"FECHA DE ALTA",
|
||||||
|
"FECHA ALTA",
|
||||||
|
]
|
||||||
|
for i, nline in enumerate(norm_lines):
|
||||||
|
if not any(key in nline for key in keys):
|
||||||
|
continue
|
||||||
|
match = re.search(
|
||||||
|
r"(EGRESO|EGRESA|ALTA)[^0-9]{0,20}(\d{1,2}[/-]\d{1,2}[/-]\d{2,4})",
|
||||||
|
nline,
|
||||||
|
)
|
||||||
|
date = match.group(2) if match else None
|
||||||
|
if not date:
|
||||||
|
date = extract_date_from_text(lines[i])
|
||||||
|
if not date and i + 1 < len(lines):
|
||||||
|
date = extract_date_from_text(lines[i + 1])
|
||||||
|
if date:
|
||||||
|
time = extract_time_from_text(lines[i]) or (
|
||||||
|
extract_time_from_text(lines[i + 1]) if i + 1 < len(lines) else None
|
||||||
|
)
|
||||||
|
return f"{date} {time}" if time else date
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
def clean_ips_name(value):
|
def clean_ips_name(value):
|
||||||
if not value:
|
if not value:
|
||||||
return ""
|
return ""
|
||||||
@ -549,6 +628,8 @@ def merge_response(base, extra):
|
|||||||
"fecha_ingreso_urgencias"
|
"fecha_ingreso_urgencias"
|
||||||
):
|
):
|
||||||
result["fecha_ingreso_urgencias"] = extra.get("fecha_ingreso_urgencias")
|
result["fecha_ingreso_urgencias"] = extra.get("fecha_ingreso_urgencias")
|
||||||
|
if not result.get("fecha_egreso") and extra.get("fecha_egreso"):
|
||||||
|
result["fecha_egreso"] = extra.get("fecha_egreso")
|
||||||
if not result.get("cups_busqueda") and extra.get("cups_busqueda"):
|
if not result.get("cups_busqueda") and extra.get("cups_busqueda"):
|
||||||
result["cups_busqueda"] = extra.get("cups_busqueda")
|
result["cups_busqueda"] = extra.get("cups_busqueda")
|
||||||
if result.get("formato") == "DESCONOCIDO" and extra.get("formato"):
|
if result.get("formato") == "DESCONOCIDO" and extra.get("formato"):
|
||||||
@ -594,6 +675,7 @@ def build_response(text, ocr_used, ocr_available, ocr_error):
|
|||||||
cie_desc = ", ".join([d for d in cie_descs if d]) if any(cie_descs) else None
|
cie_desc = ", ".join([d for d in cie_descs if d]) if any(cie_descs) else None
|
||||||
ips_nombre, ips_nit = extract_ips(lines, norm_lines)
|
ips_nombre, ips_nit = extract_ips(lines, norm_lines)
|
||||||
fecha_ingreso = extract_fecha_ingreso_urgencias(lines, norm_lines)
|
fecha_ingreso = extract_fecha_ingreso_urgencias(lines, norm_lines)
|
||||||
|
fecha_egreso = extract_fecha_egreso(lines, norm_lines)
|
||||||
formato = detect_format(norm_text, norm_lines)
|
formato = detect_format(norm_text, norm_lines)
|
||||||
|
|
||||||
warnings = []
|
warnings = []
|
||||||
@ -627,6 +709,7 @@ def build_response(text, ocr_used, ocr_available, ocr_error):
|
|||||||
"ips_nombre": ips_nombre,
|
"ips_nombre": ips_nombre,
|
||||||
"ips_nit": ips_nit,
|
"ips_nit": ips_nit,
|
||||||
"fecha_ingreso_urgencias": fecha_ingreso,
|
"fecha_ingreso_urgencias": fecha_ingreso,
|
||||||
|
"fecha_egreso": fecha_egreso,
|
||||||
"warnings": warnings,
|
"warnings": warnings,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,48 @@
|
|||||||
const ExcelJS = require('exceljs');
|
const ExcelJS = require('exceljs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const BOGOTA_TIMEZONE = 'America/Bogota';
|
||||||
|
|
||||||
|
const getBogotaDateParts = (value) => {
|
||||||
|
if (!value) return null;
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: BOGOTA_TIMEZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const data = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part.type !== 'literal') {
|
||||||
|
data[part.type] = part.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data.year && data.month && data.day
|
||||||
|
? { year: data.year, month: data.month, day: data.day }
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildBogotaDate = (value) => {
|
||||||
|
const parts = getBogotaDateParts(value);
|
||||||
|
if (!parts) return null;
|
||||||
|
return new Date(Number(parts.year), Number(parts.month) - 1, Number(parts.day));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBogotaTime = (value) => {
|
||||||
|
if (!value) return '';
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
return new Intl.DateTimeFormat('es-CO', {
|
||||||
|
timeZone: BOGOTA_TIMEZONE,
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
async function crearLibroAutorizacion(a) {
|
async function crearLibroAutorizacion(a) {
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const sheet = workbook.addWorksheet('GAR-F-01');
|
const sheet = workbook.addWorksheet('GAR-F-01');
|
||||||
@ -2693,9 +2735,8 @@ async function crearLibroAutorizacion(a) {
|
|||||||
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2) Hora -> si no tienes hora en BD, usamos la hora actual como texto
|
// 2) Hora -> usamos la hora registrada en Bogota (o la actual si no hay fecha)
|
||||||
const ahora = new Date();
|
const horaStr = formatBogotaTime(a.fecha_autorizacion) || formatBogotaTime(new Date());
|
||||||
const horaStr = ahora.toTimeString().slice(0, 5); // "HH:MM"
|
|
||||||
['K8', 'L8', 'M8'].forEach(celda => {
|
['K8', 'L8', 'M8'].forEach(celda => {
|
||||||
sheet.getCell(celda).value = horaStr;
|
sheet.getCell(celda).value = horaStr;
|
||||||
});
|
});
|
||||||
@ -2707,11 +2748,11 @@ async function crearLibroAutorizacion(a) {
|
|||||||
sheet.getCell(celda).value = versionValor;
|
sheet.getCell(celda).value = versionValor;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (a.fecha_autorizacion) {
|
const fechaBogota = buildBogotaDate(a.fecha_autorizacion);
|
||||||
const fecha = new Date(a.fecha_autorizacion);
|
if (fechaBogota) {
|
||||||
['P8', 'Q8', 'R8'].forEach(celda => {
|
['P8', 'Q8', 'R8'].forEach(celda => {
|
||||||
const cell = sheet.getCell(celda);
|
const cell = sheet.getCell(celda);
|
||||||
cell.value = fecha;
|
cell.value = fechaBogota;
|
||||||
cell.numFmt = 'dd/mm/yyyy';
|
cell.numFmt = 'dd/mm/yyyy';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,48 @@
|
|||||||
const ExcelJS = require('exceljs');
|
const ExcelJS = require('exceljs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const BOGOTA_TIMEZONE = 'America/Bogota';
|
||||||
|
|
||||||
|
const getBogotaDateParts = (value) => {
|
||||||
|
if (!value) return null;
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return null;
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: BOGOTA_TIMEZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(date);
|
||||||
|
const data = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part.type !== 'literal') {
|
||||||
|
data[part.type] = part.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data.year && data.month && data.day
|
||||||
|
? { year: data.year, month: data.month, day: data.day }
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildBogotaDate = (value) => {
|
||||||
|
const parts = getBogotaDateParts(value);
|
||||||
|
if (!parts) return null;
|
||||||
|
return new Date(Number(parts.year), Number(parts.month) - 1, Number(parts.day));
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatBogotaTime = (value) => {
|
||||||
|
if (!value) return '';
|
||||||
|
const date = new Date(value);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
return new Intl.DateTimeFormat('es-CO', {
|
||||||
|
timeZone: BOGOTA_TIMEZONE,
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
}).format(date);
|
||||||
|
};
|
||||||
|
|
||||||
async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
||||||
const workbook = new ExcelJS.Workbook();
|
const workbook = new ExcelJS.Workbook();
|
||||||
const sheet = workbook.addWorksheet("Hoja1");
|
const sheet = workbook.addWorksheet("Hoja1");
|
||||||
@ -2368,8 +2410,7 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
|||||||
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
||||||
});
|
});
|
||||||
|
|
||||||
const ahora = new Date();
|
const horaStr = formatBogotaTime(a.fecha_autorizacion) || formatBogotaTime(new Date());
|
||||||
const horaStr = ahora.toTimeString().slice(0, 5);
|
|
||||||
['K8', 'L8', 'M8'].forEach((celda) => {
|
['K8', 'L8', 'M8'].forEach((celda) => {
|
||||||
sheet.getCell(celda).value = horaStr;
|
sheet.getCell(celda).value = horaStr;
|
||||||
});
|
});
|
||||||
@ -2380,11 +2421,11 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
|||||||
sheet.getCell(celda).value = versionValor;
|
sheet.getCell(celda).value = versionValor;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (a.fecha_autorizacion) {
|
const fechaBogota = buildBogotaDate(a.fecha_autorizacion);
|
||||||
const fecha = new Date(a.fecha_autorizacion);
|
if (fechaBogota) {
|
||||||
['P8', 'Q8', 'R8'].forEach((celda) => {
|
['P8', 'Q8', 'R8'].forEach((celda) => {
|
||||||
const cell = sheet.getCell(celda);
|
const cell = sheet.getCell(celda);
|
||||||
cell.value = fecha;
|
cell.value = fechaBogota;
|
||||||
cell.numFmt = 'dd/mm/yyyy';
|
cell.numFmt = 'dd/mm/yyyy';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -53,6 +53,13 @@ CREATE TABLE IF NOT EXISTS ips (
|
|||||||
tiene_convenio boolean DEFAULT true
|
tiene_convenio boolean DEFAULT true
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- ===== CIE-10 =====
|
||||||
|
CREATE TABLE IF NOT EXISTS cie10 (
|
||||||
|
codigo VARCHAR(20) PRIMARY KEY,
|
||||||
|
descripcion TEXT NOT NULL,
|
||||||
|
descripcion_busqueda TEXT
|
||||||
|
);
|
||||||
|
|
||||||
-- ===== Ingreso =====
|
-- ===== Ingreso =====
|
||||||
CREATE TABLE IF NOT EXISTS ingreso (
|
CREATE TABLE IF NOT EXISTS ingreso (
|
||||||
interno text PRIMARY KEY REFERENCES paciente(interno),
|
interno text PRIMARY KEY REFERENCES paciente(interno),
|
||||||
@ -181,7 +188,7 @@ CREATE TABLE IF NOT EXISTS autorizacion (
|
|||||||
id_ips integer NOT NULL REFERENCES ips(id_ips),
|
id_ips integer NOT NULL REFERENCES ips(id_ips),
|
||||||
numero_documento_autorizante bigint NOT NULL REFERENCES autorizante(numero_documento),
|
numero_documento_autorizante bigint NOT NULL REFERENCES autorizante(numero_documento),
|
||||||
id_usuario_solicitante integer REFERENCES usuario(id_usuario),
|
id_usuario_solicitante integer REFERENCES usuario(id_usuario),
|
||||||
fecha_autorizacion date NOT NULL DEFAULT current_date,
|
fecha_autorizacion TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||||
observacion text,
|
observacion text,
|
||||||
cup_codigo varchar(20),
|
cup_codigo varchar(20),
|
||||||
cie10_codigo text,
|
cie10_codigo text,
|
||||||
@ -252,6 +259,11 @@ ALTER TABLE autorizacion
|
|||||||
ALTER COLUMN correo_inpec_enviado SET DEFAULT false,
|
ALTER COLUMN correo_inpec_enviado SET DEFAULT false,
|
||||||
ALTER COLUMN correo_inpec_respuesta SET DEFAULT false;
|
ALTER COLUMN correo_inpec_respuesta SET DEFAULT false;
|
||||||
|
|
||||||
|
ALTER TABLE autorizacion
|
||||||
|
ALTER COLUMN fecha_autorizacion TYPE TIMESTAMPTZ
|
||||||
|
USING fecha_autorizacion::timestamp AT TIME ZONE 'America/Bogota',
|
||||||
|
ALTER COLUMN fecha_autorizacion SET DEFAULT now();
|
||||||
|
|
||||||
ALTER TABLE autorizacion
|
ALTER TABLE autorizacion
|
||||||
ALTER COLUMN tipo_autorizacion SET NOT NULL,
|
ALTER COLUMN tipo_autorizacion SET NOT NULL,
|
||||||
ALTER COLUMN estado_entrega SET NOT NULL,
|
ALTER COLUMN estado_entrega SET NOT NULL,
|
||||||
@ -328,7 +340,7 @@ CREATE TABLE IF NOT EXISTS autorizacion_version (
|
|||||||
id_ips INTEGER NOT NULL,
|
id_ips INTEGER NOT NULL,
|
||||||
numero_documento_autorizante BIGINT NOT NULL,
|
numero_documento_autorizante BIGINT NOT NULL,
|
||||||
id_usuario_solicitante INTEGER,
|
id_usuario_solicitante INTEGER,
|
||||||
fecha_autorizacion DATE,
|
fecha_autorizacion TIMESTAMPTZ,
|
||||||
observacion TEXT,
|
observacion TEXT,
|
||||||
cup_codigo VARCHAR(20),
|
cup_codigo VARCHAR(20),
|
||||||
cie10_codigo TEXT,
|
cie10_codigo TEXT,
|
||||||
@ -341,6 +353,10 @@ CREATE TABLE IF NOT EXISTS autorizacion_version (
|
|||||||
fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
|
fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ALTER TABLE autorizacion_version
|
||||||
|
ALTER COLUMN fecha_autorizacion TYPE TIMESTAMPTZ
|
||||||
|
USING fecha_autorizacion::timestamp AT TIME ZONE 'America/Bogota';
|
||||||
|
|
||||||
-- ===== Profesionales REPS =====
|
-- ===== Profesionales REPS =====
|
||||||
CREATE TABLE IF NOT EXISTS profesional_reps (
|
CREATE TABLE IF NOT EXISTS profesional_reps (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
|||||||
@ -111,6 +111,129 @@ const normalizeSearch = (value) =>
|
|||||||
.toUpperCase()
|
.toUpperCase()
|
||||||
.trim();
|
.trim();
|
||||||
|
|
||||||
|
const BOGOTA_TIMEZONE = 'America/Bogota';
|
||||||
|
|
||||||
|
const getBogotaNowParts = () => {
|
||||||
|
const formatter = new Intl.DateTimeFormat('en-CA', {
|
||||||
|
timeZone: BOGOTA_TIMEZONE,
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit',
|
||||||
|
second: '2-digit',
|
||||||
|
hourCycle: 'h23',
|
||||||
|
});
|
||||||
|
const parts = formatter.formatToParts(new Date());
|
||||||
|
const data = {};
|
||||||
|
for (const part of parts) {
|
||||||
|
if (part.type !== 'literal') {
|
||||||
|
data[part.type] = part.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
date: `${data.year}-${data.month}-${data.day}`,
|
||||||
|
time: `${data.hour}:${data.minute}:${data.second}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeDateInput = (value) => {
|
||||||
|
const raw = String(value || '').trim();
|
||||||
|
const match = raw.match(/^(\d{4})-(\d{2})-(\d{2})$/);
|
||||||
|
if (!match) return '';
|
||||||
|
const year = Number(match[1]);
|
||||||
|
const month = Number(match[2]);
|
||||||
|
const day = Number(match[3]);
|
||||||
|
const check = new Date(Date.UTC(year, month - 1, day));
|
||||||
|
if (
|
||||||
|
check.getUTCFullYear() !== year ||
|
||||||
|
check.getUTCMonth() + 1 !== month ||
|
||||||
|
check.getUTCDate() !== day
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${match[1]}-${match[2]}-${match[3]}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeTimeInput = (value) => {
|
||||||
|
const raw = String(value || '').trim();
|
||||||
|
if (!raw) return '';
|
||||||
|
const withoutZone = raw.split(/[Z+-]/)[0];
|
||||||
|
const cleaned = withoutZone.split('.')[0];
|
||||||
|
const match = cleaned.match(/^(\d{1,2}):(\d{2})(?::(\d{2}))?$/);
|
||||||
|
if (!match) return '';
|
||||||
|
const hour = Number(match[1]);
|
||||||
|
const minute = Number(match[2]);
|
||||||
|
const second = Number(match[3] || '0');
|
||||||
|
if (
|
||||||
|
!Number.isFinite(hour) ||
|
||||||
|
!Number.isFinite(minute) ||
|
||||||
|
!Number.isFinite(second) ||
|
||||||
|
hour < 0 ||
|
||||||
|
hour > 23 ||
|
||||||
|
minute < 0 ||
|
||||||
|
minute > 59 ||
|
||||||
|
second < 0 ||
|
||||||
|
second > 59
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${String(hour).padStart(2, '0')}:${String(minute).padStart(2, '0')}:${String(second).padStart(2, '0')}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const splitDateTimeInput = (raw) => {
|
||||||
|
const value = String(raw || '').trim();
|
||||||
|
if (!value) return { datePart: '', timePart: '' };
|
||||||
|
const [datePart, timePart = ''] = value.split(/[T ]/);
|
||||||
|
return { datePart, timePart };
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFechaAutorizacionInput = (fechaValue, horaValue, fallbackNow) => {
|
||||||
|
const { datePart, timePart } = splitDateTimeInput(fechaValue);
|
||||||
|
const date = normalizeDateInput(datePart);
|
||||||
|
const timeFromFecha = normalizeTimeInput(timePart);
|
||||||
|
const timeFromHora = normalizeTimeInput(horaValue);
|
||||||
|
|
||||||
|
if (!date && !fallbackNow) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nowParts = getBogotaNowParts();
|
||||||
|
const finalDate = date || nowParts.date;
|
||||||
|
const finalTime = timeFromFecha || timeFromHora || nowParts.time;
|
||||||
|
return `${finalDate} ${finalTime}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildFechaHoraRange = (fechaInicio, fechaFin, horaInicio, horaFin) => {
|
||||||
|
const inicioDate = normalizeDateInput(fechaInicio);
|
||||||
|
const finDate = normalizeDateInput(fechaFin);
|
||||||
|
if (!inicioDate || !finDate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const inicioTime = normalizeTimeInput(horaInicio) || '00:00:00';
|
||||||
|
const finTime = normalizeTimeInput(horaFin) || '23:59:59';
|
||||||
|
return {
|
||||||
|
inicio: `${inicioDate} ${inicioTime}`,
|
||||||
|
fin: `${finDate} ${finTime}`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateFechaHoraInput = (fechaValue, horaValue) => {
|
||||||
|
const rawFecha = String(fechaValue || '').trim();
|
||||||
|
const rawHora = String(horaValue || '').trim();
|
||||||
|
const { datePart, timePart } = splitDateTimeInput(rawFecha);
|
||||||
|
if (rawFecha && !normalizeDateInput(datePart)) {
|
||||||
|
return { error: 'fecha_autorizacion invalida' };
|
||||||
|
}
|
||||||
|
if (timePart && !normalizeTimeInput(timePart)) {
|
||||||
|
return { error: 'hora en fecha_autorizacion invalida' };
|
||||||
|
}
|
||||||
|
if (rawHora && !normalizeTimeInput(rawHora)) {
|
||||||
|
return { error: 'hora_autorizacion invalida' };
|
||||||
|
}
|
||||||
|
return { error: null };
|
||||||
|
};
|
||||||
|
|
||||||
const isPdfFile = (file) => {
|
const isPdfFile = (file) => {
|
||||||
if (!file) return false;
|
if (!file) return false;
|
||||||
const mime = String(file.mimetype || '').toLowerCase();
|
const mime = String(file.mimetype || '').toLowerCase();
|
||||||
@ -350,6 +473,21 @@ const ensureCupsTables = async () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ensureCie10Table = async () => {
|
||||||
|
await pool.query(`
|
||||||
|
CREATE TABLE IF NOT EXISTS cie10 (
|
||||||
|
codigo VARCHAR(20) PRIMARY KEY,
|
||||||
|
descripcion TEXT NOT NULL,
|
||||||
|
descripcion_busqueda TEXT
|
||||||
|
);
|
||||||
|
`);
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE cie10
|
||||||
|
ADD COLUMN IF NOT EXISTS descripcion_busqueda TEXT;
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
const ensureAutorizacionVersionTables = async () => {
|
const ensureAutorizacionVersionTables = async () => {
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
CREATE TABLE IF NOT EXISTS autorizacion_version (
|
CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||||
@ -359,7 +497,7 @@ const ensureAutorizacionVersionTables = async () => {
|
|||||||
id_ips INTEGER NOT NULL,
|
id_ips INTEGER NOT NULL,
|
||||||
numero_documento_autorizante BIGINT NOT NULL,
|
numero_documento_autorizante BIGINT NOT NULL,
|
||||||
id_usuario_solicitante INTEGER,
|
id_usuario_solicitante INTEGER,
|
||||||
fecha_autorizacion DATE,
|
fecha_autorizacion TIMESTAMPTZ,
|
||||||
observacion TEXT,
|
observacion TEXT,
|
||||||
cup_codigo VARCHAR(20),
|
cup_codigo VARCHAR(20),
|
||||||
cie10_codigo VARCHAR(20),
|
cie10_codigo VARCHAR(20),
|
||||||
@ -373,6 +511,24 @@ const ensureAutorizacionVersionTables = async () => {
|
|||||||
);
|
);
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
const versionFechaRes = await pool.query(
|
||||||
|
`
|
||||||
|
SELECT data_type
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'autorizacion_version'
|
||||||
|
AND column_name = 'fecha_autorizacion'
|
||||||
|
`
|
||||||
|
);
|
||||||
|
const versionFechaTipo = versionFechaRes.rows[0]?.data_type;
|
||||||
|
if (versionFechaTipo && versionFechaTipo !== 'timestamp with time zone') {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE autorizacion_version
|
||||||
|
ALTER COLUMN fecha_autorizacion TYPE TIMESTAMPTZ
|
||||||
|
USING fecha_autorizacion::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}';
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
CREATE UNIQUE INDEX IF NOT EXISTS autorizacion_version_unique
|
CREATE UNIQUE INDEX IF NOT EXISTS autorizacion_version_unique
|
||||||
ON autorizacion_version (numero_autorizacion, version);
|
ON autorizacion_version (numero_autorizacion, version);
|
||||||
@ -540,6 +696,31 @@ const ensureAutorizacionExtras = async () => {
|
|||||||
`);
|
`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const ensureAutorizacionFechaHora = async () => {
|
||||||
|
const fechaRes = await pool.query(
|
||||||
|
`
|
||||||
|
SELECT data_type
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_schema = 'public'
|
||||||
|
AND table_name = 'autorizacion'
|
||||||
|
AND column_name = 'fecha_autorizacion'
|
||||||
|
`
|
||||||
|
);
|
||||||
|
const fechaTipo = fechaRes.rows[0]?.data_type;
|
||||||
|
if (fechaTipo && fechaTipo !== 'timestamp with time zone') {
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE autorizacion
|
||||||
|
ALTER COLUMN fecha_autorizacion TYPE TIMESTAMPTZ
|
||||||
|
USING fecha_autorizacion::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}';
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await pool.query(`
|
||||||
|
ALTER TABLE autorizacion
|
||||||
|
ALTER COLUMN fecha_autorizacion SET DEFAULT now();
|
||||||
|
`);
|
||||||
|
};
|
||||||
|
|
||||||
const ensureIpsConvenio = async () => {
|
const ensureIpsConvenio = async () => {
|
||||||
await pool.query(`
|
await pool.query(`
|
||||||
ALTER TABLE ips
|
ALTER TABLE ips
|
||||||
@ -994,6 +1175,52 @@ app.post(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
app.post(
|
||||||
|
'/api/cargar-cie10',
|
||||||
|
verificarToken,
|
||||||
|
esAdministrador,
|
||||||
|
upload.single('archivo'),
|
||||||
|
async (req, res) => {
|
||||||
|
try {
|
||||||
|
if (!req.file) {
|
||||||
|
return res.status(400).json({ error: 'No se recibio archivo Excel' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const jobId = createJobId();
|
||||||
|
const job = {
|
||||||
|
id: jobId,
|
||||||
|
type: 'cie10-carga',
|
||||||
|
status: 'queued',
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
ownerId: req.usuario.id_usuario,
|
||||||
|
};
|
||||||
|
|
||||||
|
jobs.set(jobId, job);
|
||||||
|
|
||||||
|
const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
|
||||||
|
await fsPromises.mkdir(jobTmpDir, { recursive: true });
|
||||||
|
const inputPath = path.join(jobTmpDir, `cie10_${jobId}.xlsx`);
|
||||||
|
await fsPromises.writeFile(inputPath, req.file.buffer);
|
||||||
|
|
||||||
|
enqueueJob(job, async () => {
|
||||||
|
try {
|
||||||
|
return await procesarExcelCie10(inputPath);
|
||||||
|
} finally {
|
||||||
|
await safeUnlink(inputPath);
|
||||||
|
await safeRemoveDir(jobTmpDir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(202).json(sanitizeJob(job));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creando job de CIE-10:', error);
|
||||||
|
return res.status(500).json({
|
||||||
|
error: 'Error creando el job para procesar el Excel de CIE-10',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
app.post(
|
app.post(
|
||||||
'/api/cargar-ips',
|
'/api/cargar-ips',
|
||||||
verificarToken,
|
verificarToken,
|
||||||
@ -1507,6 +1734,24 @@ const normalizeDigits = (value) => String(value || '').replace(/\D/g, '');
|
|||||||
const normalizeNameKey = (value) =>
|
const normalizeNameKey = (value) =>
|
||||||
normalizeSearch(value).replace(/[^A-Z0-9]/g, '');
|
normalizeSearch(value).replace(/[^A-Z0-9]/g, '');
|
||||||
|
|
||||||
|
const normalizeLocationKey = (value) =>
|
||||||
|
normalizeSearch(value).replace(/[^A-Z0-9]/g, '');
|
||||||
|
|
||||||
|
const pickIpsRowByLocation = (rows, departamentoKey, municipioKey) => {
|
||||||
|
if (!rows || rows.length === 0) return null;
|
||||||
|
if (!departamentoKey && !municipioKey) {
|
||||||
|
return rows[0];
|
||||||
|
}
|
||||||
|
const match = rows.find((row) => {
|
||||||
|
const rowDep = normalizeLocationKey(row.departamento || '');
|
||||||
|
const rowMun = normalizeLocationKey(row.municipio || '');
|
||||||
|
if (departamentoKey && rowDep && departamentoKey !== rowDep) return false;
|
||||||
|
if (municipioKey && rowMun && municipioKey !== rowMun) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
return match || null;
|
||||||
|
};
|
||||||
|
|
||||||
const IPS_EMPTY_MARKERS = new Set([
|
const IPS_EMPTY_MARKERS = new Set([
|
||||||
'NA',
|
'NA',
|
||||||
'SD',
|
'SD',
|
||||||
@ -1529,6 +1774,108 @@ const cleanIpsField = (value) => {
|
|||||||
return raw;
|
return raw;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
async function procesarExcelCie10(inputFilePath) {
|
||||||
|
await ensureCie10Table();
|
||||||
|
|
||||||
|
const workbook = new ExcelJS.Workbook();
|
||||||
|
await workbook.xlsx.readFile(inputFilePath);
|
||||||
|
const sheet = workbook.worksheets[0];
|
||||||
|
|
||||||
|
if (!sheet) {
|
||||||
|
throw new Error('No se encontro una hoja en el Excel de CIE-10');
|
||||||
|
}
|
||||||
|
|
||||||
|
const headerRow = sheet.getRow(1);
|
||||||
|
const headers = {};
|
||||||
|
headerRow.eachCell((cell, colNumber) => {
|
||||||
|
const base = normalizeHeader(getExcelCellText(cell.value));
|
||||||
|
if (base) headers[base] = colNumber;
|
||||||
|
});
|
||||||
|
|
||||||
|
const getValue = (row, key) => {
|
||||||
|
const col = headers[key];
|
||||||
|
if (!col) return '';
|
||||||
|
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,
|
||||||
|
omitidas: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const client = await pool.connect();
|
||||||
|
try {
|
||||||
|
await client.query('BEGIN');
|
||||||
|
|
||||||
|
for (let i = 2; i <= sheet.rowCount; i++) {
|
||||||
|
const row = sheet.getRow(i);
|
||||||
|
let codigo = getValueMulti(row, [
|
||||||
|
'CODIGOCIE10',
|
||||||
|
'CODIGOCIE',
|
||||||
|
'CODIGODELACIE10CUATROCARACTERES',
|
||||||
|
'CODIGO',
|
||||||
|
]);
|
||||||
|
let descripcion = getValueMulti(row, [
|
||||||
|
'DESCRIPCIONCIE10',
|
||||||
|
'DESCRIPCION',
|
||||||
|
'DESCRIPCIONDECODIGOSACUATROCARACTERES',
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!codigo && !descripcion) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
codigo = String(codigo || '').trim().toUpperCase();
|
||||||
|
descripcion = String(descripcion || '').trim();
|
||||||
|
|
||||||
|
if (!codigo || !descripcion) {
|
||||||
|
resumen.omitidas += 1;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const descripcionBusqueda = normalizeSearch(descripcion);
|
||||||
|
|
||||||
|
await client.query(
|
||||||
|
`
|
||||||
|
INSERT INTO cie10 (codigo, descripcion, descripcion_busqueda)
|
||||||
|
VALUES ($1, $2, $3)
|
||||||
|
ON CONFLICT (codigo) DO UPDATE
|
||||||
|
SET descripcion = EXCLUDED.descripcion,
|
||||||
|
descripcion_busqueda = EXCLUDED.descripcion_busqueda
|
||||||
|
`,
|
||||||
|
[codigo, descripcion, descripcionBusqueda]
|
||||||
|
);
|
||||||
|
|
||||||
|
resumen.total += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRes = await client.query('SELECT COUNT(*)::int AS total FROM cie10');
|
||||||
|
|
||||||
|
await client.query('COMMIT');
|
||||||
|
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
mensaje: 'CIE-10 cargado correctamente',
|
||||||
|
procesadas: resumen.total,
|
||||||
|
omitidas: resumen.omitidas,
|
||||||
|
total: totalRes.rows[0]?.total ?? null,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
await client.query('ROLLBACK');
|
||||||
|
throw error;
|
||||||
|
} finally {
|
||||||
|
client.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const extractCupCodigo = (value) => {
|
const extractCupCodigo = (value) => {
|
||||||
const text = String(value || '').trim();
|
const text = String(value || '').trim();
|
||||||
if (!text) return '';
|
if (!text) return '';
|
||||||
@ -1730,54 +2077,65 @@ async function procesarExcelIps(inputFilePath) {
|
|||||||
|
|
||||||
const nombreKey = normalizeNameKey(nombre);
|
const nombreKey = normalizeNameKey(nombre);
|
||||||
if (nombreKey) nombreKeys.add(nombreKey);
|
if (nombreKey) nombreKeys.add(nombreKey);
|
||||||
|
const departamentoKey = normalizeLocationKey(departamento);
|
||||||
|
const municipioKey = normalizeLocationKey(municipio);
|
||||||
|
|
||||||
let existente = null;
|
let existente = null;
|
||||||
if (lookupKeys.length > 0) {
|
if (lookupKeys.length > 0) {
|
||||||
const res = await client.query(
|
const res = await client.query(
|
||||||
`
|
`
|
||||||
SELECT id_ips
|
SELECT id_ips, departamento, municipio
|
||||||
FROM ips
|
FROM ips
|
||||||
WHERE regexp_replace(nit, '\\D', '', 'g') = ANY($1::text[])
|
WHERE regexp_replace(nit, '\\D', '', 'g') = ANY($1::text[])
|
||||||
OR regexp_replace(codigo_ips, '\\D', '', 'g') = ANY($1::text[])
|
OR regexp_replace(codigo_ips, '\\D', '', 'g') = ANY($1::text[])
|
||||||
LIMIT 1
|
|
||||||
`,
|
`,
|
||||||
[lookupKeys]
|
[lookupKeys]
|
||||||
);
|
);
|
||||||
existente = res.rows[0] || null;
|
existente = pickIpsRowByLocation(
|
||||||
|
res.rows,
|
||||||
|
departamentoKey,
|
||||||
|
municipioKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existente && nitDigits && nitDigits.length >= 8) {
|
if (!existente && nitDigits && nitDigits.length >= 8) {
|
||||||
const res = await client.query(
|
const res = await client.query(
|
||||||
`
|
`
|
||||||
SELECT id_ips
|
SELECT id_ips, departamento, municipio
|
||||||
FROM ips
|
FROM ips
|
||||||
WHERE regexp_replace(nit, '\\D', '', 'g') LIKE $1
|
WHERE regexp_replace(nit, '\\D', '', 'g') LIKE $1
|
||||||
AND length(regexp_replace(nit, '\\D', '', 'g')) = $2
|
AND length(regexp_replace(nit, '\\D', '', 'g')) = $2
|
||||||
LIMIT 1
|
|
||||||
`,
|
`,
|
||||||
[`${nitDigits}%`, nitDigits.length + 1]
|
[`${nitDigits}%`, nitDigits.length + 1]
|
||||||
);
|
);
|
||||||
existente = res.rows[0] || null;
|
existente = pickIpsRowByLocation(
|
||||||
|
res.rows,
|
||||||
|
departamentoKey,
|
||||||
|
municipioKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existente && codigoDigits && codigoDigits.length >= 8) {
|
if (!existente && codigoDigits && codigoDigits.length >= 8) {
|
||||||
const res = await client.query(
|
const res = await client.query(
|
||||||
`
|
`
|
||||||
SELECT id_ips
|
SELECT id_ips, departamento, municipio
|
||||||
FROM ips
|
FROM ips
|
||||||
WHERE regexp_replace(codigo_ips, '\\D', '', 'g') LIKE $1
|
WHERE regexp_replace(codigo_ips, '\\D', '', 'g') LIKE $1
|
||||||
AND length(regexp_replace(codigo_ips, '\\D', '', 'g')) = $2
|
AND length(regexp_replace(codigo_ips, '\\D', '', 'g')) = $2
|
||||||
LIMIT 1
|
|
||||||
`,
|
`,
|
||||||
[`${codigoDigits}%`, codigoDigits.length + 1]
|
[`${codigoDigits}%`, codigoDigits.length + 1]
|
||||||
);
|
);
|
||||||
existente = res.rows[0] || null;
|
existente = pickIpsRowByLocation(
|
||||||
|
res.rows,
|
||||||
|
departamentoKey,
|
||||||
|
municipioKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!existente && nombreKey) {
|
if (!existente && nombreKey) {
|
||||||
const res = await client.query(
|
const res = await client.query(
|
||||||
`
|
`
|
||||||
SELECT id_ips
|
SELECT id_ips, departamento, municipio
|
||||||
FROM ips
|
FROM ips
|
||||||
WHERE regexp_replace(
|
WHERE regexp_replace(
|
||||||
translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
|
translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
|
||||||
@ -1785,11 +2143,14 @@ async function procesarExcelIps(inputFilePath) {
|
|||||||
'',
|
'',
|
||||||
'g'
|
'g'
|
||||||
) = $1
|
) = $1
|
||||||
LIMIT 1
|
|
||||||
`,
|
`,
|
||||||
[nombreKey]
|
[nombreKey]
|
||||||
);
|
);
|
||||||
existente = res.rows[0] || null;
|
existente = pickIpsRowByLocation(
|
||||||
|
res.rows,
|
||||||
|
departamentoKey,
|
||||||
|
municipioKey
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existente) {
|
if (existente) {
|
||||||
@ -2601,7 +2962,7 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) {
|
|||||||
`
|
`
|
||||||
INSERT INTO autorizacion
|
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)
|
(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')
|
VALUES ($1, $2, $3, $4, COALESCE($5::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}', now()), $6, $7, $8, $9, $10, $11, $12, $13, $14, 'pendiente')
|
||||||
RETURNING numero_autorizacion, fecha_autorizacion, version;
|
RETURNING numero_autorizacion, fecha_autorizacion, version;
|
||||||
`,
|
`,
|
||||||
[
|
[
|
||||||
@ -2879,7 +3240,9 @@ async function generarZipAutorizacionesPorFecha(
|
|||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereParts = ['a.fecha_autorizacion BETWEEN $1 AND $2'];
|
const whereParts = [
|
||||||
|
`a.fecha_autorizacion BETWEEN ($1::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}') AND ($2::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}')`,
|
||||||
|
];
|
||||||
const params = [fechaInicio, fechaFin];
|
const params = [fechaInicio, fechaFin];
|
||||||
const joinParts = [];
|
const joinParts = [];
|
||||||
if (soloAutorizadas) {
|
if (soloAutorizadas) {
|
||||||
@ -2930,9 +3293,15 @@ async function generarZipAutorizacionesPorFecha(
|
|||||||
const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
|
const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
|
||||||
await fsPromises.mkdir(jobTmpDir, { recursive: true });
|
await fsPromises.mkdir(jobTmpDir, { recursive: true });
|
||||||
|
|
||||||
|
const safeInicio = String(fechaInicio)
|
||||||
|
.replace(/[:]/g, '')
|
||||||
|
.replace(/\s+/g, '_');
|
||||||
|
const safeFin = String(fechaFin)
|
||||||
|
.replace(/[:]/g, '')
|
||||||
|
.replace(/\s+/g, '_');
|
||||||
const zipPath = path.join(
|
const zipPath = path.join(
|
||||||
jobTmpDir,
|
jobTmpDir,
|
||||||
`autorizaciones_${fechaInicio}_${fechaFin}.zip`
|
`autorizaciones_${safeInicio}_${safeFin}.zip`
|
||||||
);
|
);
|
||||||
|
|
||||||
const output = fs.createWriteStream(zipPath);
|
const output = fs.createWriteStream(zipPath);
|
||||||
@ -3129,6 +3498,8 @@ const crearJobPdfHandler = async (req, res) => {
|
|||||||
const crearJobZipHandler = async (req, res) => {
|
const crearJobZipHandler = async (req, res) => {
|
||||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||||
|
const hora_inicio = req.body?.hora_inicio || req.query?.hora_inicio;
|
||||||
|
const hora_fin = req.body?.hora_fin || req.query?.hora_fin;
|
||||||
const establecimiento = req.body?.establecimiento || req.query?.establecimiento;
|
const establecimiento = req.body?.establecimiento || req.query?.establecimiento;
|
||||||
const ambito = req.body?.ambito || req.query?.ambito;
|
const ambito = req.body?.ambito || req.query?.ambito;
|
||||||
const ips = req.body?.ips || req.query?.ips;
|
const ips = req.body?.ips || req.query?.ips;
|
||||||
@ -3139,6 +3510,21 @@ const crearJobPdfHandler = async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
|
return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hora_inicio && !normalizeTimeInput(hora_inicio)) {
|
||||||
|
return res.status(400).json({ error: 'hora_inicio invalida' });
|
||||||
|
}
|
||||||
|
if (hora_fin && !normalizeTimeInput(hora_fin)) {
|
||||||
|
return res.status(400).json({ error: 'hora_fin invalida' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rango = buildFechaHoraRange(fecha_inicio, fecha_fin, hora_inicio, hora_fin);
|
||||||
|
if (!rango) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio o fecha_fin invalida' });
|
||||||
|
}
|
||||||
|
if (rango.inicio > rango.fin) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio no puede ser mayor que fecha_fin' });
|
||||||
|
}
|
||||||
|
|
||||||
const jobId = createJobId();
|
const jobId = createJobId();
|
||||||
const job = {
|
const job = {
|
||||||
id: jobId,
|
id: jobId,
|
||||||
@ -3151,17 +3537,19 @@ const crearJobPdfHandler = async (req, res) => {
|
|||||||
jobs.set(jobId, job);
|
jobs.set(jobId, job);
|
||||||
|
|
||||||
enqueueJob(job, async () => {
|
enqueueJob(job, async () => {
|
||||||
const zipPath = await generarZipAutorizacionesPorFecha(fecha_inicio, fecha_fin, jobId, {
|
const zipPath = await generarZipAutorizacionesPorFecha(rango.inicio, rango.fin, jobId, {
|
||||||
establecimiento,
|
establecimiento,
|
||||||
ambito,
|
ambito,
|
||||||
ips,
|
ips,
|
||||||
soloAutorizadas,
|
soloAutorizadas,
|
||||||
});
|
});
|
||||||
|
const safeInicio = String(rango.inicio).replace(/[:]/g, '').replace(/\s+/g, '_');
|
||||||
|
const safeFin = String(rango.fin).replace(/[:]/g, '').replace(/\s+/g, '_');
|
||||||
return {
|
return {
|
||||||
ok: true,
|
ok: true,
|
||||||
mensaje: 'ZIP generado correctamente',
|
mensaje: 'ZIP generado correctamente',
|
||||||
filePath: zipPath,
|
filePath: zipPath,
|
||||||
fileName: `autorizaciones_${fecha_inicio}_${fecha_fin}.zip`,
|
fileName: `autorizaciones_${safeInicio}_${safeFin}.zip`,
|
||||||
contentType: 'application/zip',
|
contentType: 'application/zip',
|
||||||
downloadUrl: `/api/jobs/${jobId}/download`,
|
downloadUrl: `/api/jobs/${jobId}/download`,
|
||||||
};
|
};
|
||||||
@ -3242,6 +3630,48 @@ app.get('/api/cups', verificarToken, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CIE-10
|
||||||
|
app.get('/api/cie10', verificarToken, async (req, res) => {
|
||||||
|
const codigo = String(req.query.codigo || '').trim().toUpperCase();
|
||||||
|
const qRaw = req.query.q || '';
|
||||||
|
const q = String(qRaw).trim();
|
||||||
|
const qNormalized = normalizeSearch(q);
|
||||||
|
const limit = Math.min(Number(req.query.limit) || 20, 200);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (codigo) {
|
||||||
|
const { rows } = await pool.query(
|
||||||
|
'SELECT codigo, descripcion FROM cie10 WHERE codigo = $1 LIMIT 1',
|
||||||
|
[codigo]
|
||||||
|
);
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return res.status(404).json({ error: 'CIE-10 no encontrado' });
|
||||||
|
}
|
||||||
|
return res.json(rows[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!q) {
|
||||||
|
return res.status(400).json({ error: 'q o codigo son requeridos' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = [`%${q}%`, `%${qNormalized}%`, limit];
|
||||||
|
const sql = `
|
||||||
|
SELECT codigo, descripcion
|
||||||
|
FROM cie10
|
||||||
|
WHERE codigo ILIKE $1
|
||||||
|
OR descripcion ILIKE $1
|
||||||
|
OR descripcion_busqueda LIKE $2
|
||||||
|
ORDER BY codigo
|
||||||
|
LIMIT $3;
|
||||||
|
`;
|
||||||
|
const { rows } = await pool.query(sql, params);
|
||||||
|
return res.json(rows);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error en /api/cie10:', error.message);
|
||||||
|
return res.status(500).json({ error: 'Error consultando CIE-10' });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
app.get('/api/pacientes', verificarToken, async (req, res) => {
|
app.get('/api/pacientes', verificarToken, async (req, res) => {
|
||||||
const { numero_documento, interno, nombre } = req.query;
|
const { numero_documento, interno, nombre } = req.query;
|
||||||
|
|
||||||
@ -3632,6 +4062,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
|||||||
numero_documento_autorizante,
|
numero_documento_autorizante,
|
||||||
observacion,
|
observacion,
|
||||||
fecha_autorizacion,
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion,
|
||||||
cup_codigo,
|
cup_codigo,
|
||||||
cie10_codigo,
|
cie10_codigo,
|
||||||
cie10_descripcion,
|
cie10_descripcion,
|
||||||
@ -3653,6 +4084,19 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
|||||||
return res.status(400).json({ error: 'cup_codigo es obligatorio' });
|
return res.status(400).json({ error: 'cup_codigo es obligatorio' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fechaHoraValidacion = validateFechaHoraInput(
|
||||||
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion
|
||||||
|
);
|
||||||
|
if (fechaHoraValidacion.error) {
|
||||||
|
return res.status(400).json({ error: fechaHoraValidacion.error });
|
||||||
|
}
|
||||||
|
const fechaAutorizacionInput = buildFechaAutorizacionInput(
|
||||||
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
const cie10Codigo = String(cie10_codigo || '').trim().toUpperCase();
|
const cie10Codigo = String(cie10_codigo || '').trim().toUpperCase();
|
||||||
const cie10Descripcion = String(cie10_descripcion || '').trim();
|
const cie10Descripcion = String(cie10_descripcion || '').trim();
|
||||||
if (!cie10Codigo) {
|
if (!cie10Codigo) {
|
||||||
@ -3745,7 +4189,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
|||||||
numero_orden,
|
numero_orden,
|
||||||
estado_entrega
|
estado_entrega
|
||||||
)
|
)
|
||||||
VALUES ($1, $2, $3, $4, COALESCE($5::date, current_date), $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
VALUES ($1, $2, $3, $4, COALESCE($5::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}', now()), $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;
|
RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega, version, estado_autorizacion;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@ -3754,7 +4198,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
|||||||
id_ips,
|
id_ips,
|
||||||
numero_documento_autorizante,
|
numero_documento_autorizante,
|
||||||
solicitanteId,
|
solicitanteId,
|
||||||
fecha_autorizacion || null,
|
fechaAutorizacionInput,
|
||||||
observacion || null,
|
observacion || null,
|
||||||
cupCodigo,
|
cupCodigo,
|
||||||
cie10Codigo,
|
cie10Codigo,
|
||||||
@ -3884,6 +4328,7 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
|||||||
numero_documento_autorizante,
|
numero_documento_autorizante,
|
||||||
observacion,
|
observacion,
|
||||||
fecha_autorizacion,
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion,
|
||||||
cup_codigo,
|
cup_codigo,
|
||||||
cie10_codigo,
|
cie10_codigo,
|
||||||
cie10_descripcion,
|
cie10_descripcion,
|
||||||
@ -3905,6 +4350,19 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
|||||||
return res.status(400).json({ error: 'cup_codigo es obligatorio' });
|
return res.status(400).json({ error: 'cup_codigo es obligatorio' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fechaHoraValidacion = validateFechaHoraInput(
|
||||||
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion
|
||||||
|
);
|
||||||
|
if (fechaHoraValidacion.error) {
|
||||||
|
return res.status(400).json({ error: fechaHoraValidacion.error });
|
||||||
|
}
|
||||||
|
const fechaAutorizacionInput = buildFechaAutorizacionInput(
|
||||||
|
fecha_autorizacion,
|
||||||
|
hora_autorizacion,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
const cie10CodigoInput = String(cie10_codigo || '').trim().toUpperCase();
|
const cie10CodigoInput = String(cie10_codigo || '').trim().toUpperCase();
|
||||||
const cie10DescripcionInput = String(cie10_descripcion || '').trim();
|
const cie10DescripcionInput = String(cie10_descripcion || '').trim();
|
||||||
const ambitoInput = String(ambito_atencion || '').trim().toLowerCase();
|
const ambitoInput = String(ambito_atencion || '').trim().toLowerCase();
|
||||||
@ -4034,14 +4492,14 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
|||||||
}
|
}
|
||||||
|
|
||||||
const nuevaVersion = versionActual + 1;
|
const nuevaVersion = versionActual + 1;
|
||||||
const fechaAut = fecha_autorizacion || actual.fecha_autorizacion || null;
|
const fechaAut = fechaAutorizacionInput || null;
|
||||||
|
|
||||||
const updateRes = await client.query(
|
const updateRes = await client.query(
|
||||||
`
|
`
|
||||||
UPDATE autorizacion
|
UPDATE autorizacion
|
||||||
SET id_ips = $1,
|
SET id_ips = $1,
|
||||||
numero_documento_autorizante = $2,
|
numero_documento_autorizante = $2,
|
||||||
fecha_autorizacion = COALESCE($3::date, fecha_autorizacion),
|
fecha_autorizacion = COALESCE($3::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}', fecha_autorizacion),
|
||||||
observacion = $4,
|
observacion = $4,
|
||||||
cup_codigo = $5,
|
cup_codigo = $5,
|
||||||
cie10_codigo = $6,
|
cie10_codigo = $6,
|
||||||
@ -4721,6 +5179,7 @@ app.patch('/api/usuarios/:id/estado', verificarToken, esAdministrador, async (re
|
|||||||
app.patch('/api/usuarios/:id', verificarToken, esAdministrador, async (req, res) => {
|
app.patch('/api/usuarios/:id', verificarToken, esAdministrador, async (req, res) => {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { username, password, email, nombre_completo, id_rol } = req.body || {};
|
const { username, password, email, nombre_completo, id_rol } = req.body || {};
|
||||||
|
let idRolActualizado = null;
|
||||||
|
|
||||||
const idUsuario = Number(id);
|
const idUsuario = Number(id);
|
||||||
if (!Number.isFinite(idUsuario)) {
|
if (!Number.isFinite(idUsuario)) {
|
||||||
@ -4888,6 +5347,8 @@ app.get('/api/roles', verificarToken, esAdministrador, async (req, res) => {
|
|||||||
*/
|
*/
|
||||||
app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => {
|
app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => {
|
||||||
const { fecha_inicio, fecha_fin } = req.query;
|
const { fecha_inicio, fecha_fin } = req.query;
|
||||||
|
const hora_inicio = req.query.hora_inicio;
|
||||||
|
const hora_fin = req.query.hora_fin;
|
||||||
const establecimiento = String(req.query.establecimiento || '').trim();
|
const establecimiento = String(req.query.establecimiento || '').trim();
|
||||||
const ambito = String(req.query.ambito || '').trim().toLowerCase();
|
const ambito = String(req.query.ambito || '').trim().toLowerCase();
|
||||||
const ips = String(req.query.ips || '').trim();
|
const ips = String(req.query.ips || '').trim();
|
||||||
@ -4905,8 +5366,25 @@ app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => {
|
|||||||
return res.status(400).json({ error: 'ambito invalido' });
|
return res.status(400).json({ error: 'ambito invalido' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereParts = ['a.fecha_autorizacion BETWEEN $1 AND $2'];
|
if (hora_inicio && !normalizeTimeInput(hora_inicio)) {
|
||||||
const params = [fecha_inicio, fecha_fin];
|
return res.status(400).json({ error: 'hora_inicio invalida' });
|
||||||
|
}
|
||||||
|
if (hora_fin && !normalizeTimeInput(hora_fin)) {
|
||||||
|
return res.status(400).json({ error: 'hora_fin invalida' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rango = buildFechaHoraRange(fecha_inicio, fecha_fin, hora_inicio, hora_fin);
|
||||||
|
if (!rango) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio o fecha_fin invalida' });
|
||||||
|
}
|
||||||
|
if (rango.inicio > rango.fin) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio no puede ser mayor que fecha_fin' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const whereParts = [
|
||||||
|
`a.fecha_autorizacion BETWEEN ($1::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}') AND ($2::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}')`,
|
||||||
|
];
|
||||||
|
const params = [rango.inicio, rango.fin];
|
||||||
|
|
||||||
if (!esAdmin) {
|
if (!esAdmin) {
|
||||||
whereParts.push("COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')");
|
whereParts.push("COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')");
|
||||||
@ -5017,7 +5495,8 @@ app.get('/api/autorizaciones-estadisticas', verificarToken, esAdministrador, asy
|
|||||||
COUNT(*) FILTER (WHERE COALESCE(estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes,
|
COUNT(*) FILTER (WHERE COALESCE(estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes,
|
||||||
COUNT(*) AS total
|
COUNT(*) AS total
|
||||||
FROM autorizacion
|
FROM autorizacion
|
||||||
WHERE fecha_autorizacion BETWEEN $1::date AND $2::date;
|
WHERE (fecha_autorizacion AT TIME ZONE '${BOGOTA_TIMEZONE}')::date
|
||||||
|
BETWEEN $1::date AND $2::date;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const diasSql = `
|
const diasSql = `
|
||||||
@ -5032,7 +5511,7 @@ app.get('/api/autorizaciones-estadisticas', verificarToken, esAdministrador, asy
|
|||||||
COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes
|
COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes
|
||||||
FROM dias d
|
FROM dias d
|
||||||
LEFT JOIN autorizacion a
|
LEFT JOIN autorizacion a
|
||||||
ON a.fecha_autorizacion::date = d.fecha
|
ON (a.fecha_autorizacion AT TIME ZONE '${BOGOTA_TIMEZONE}')::date = d.fecha
|
||||||
GROUP BY d.fecha
|
GROUP BY d.fecha
|
||||||
ORDER BY d.fecha;
|
ORDER BY d.fecha;
|
||||||
`;
|
`;
|
||||||
@ -5064,6 +5543,8 @@ app.get('/api/autorizaciones-estadisticas', verificarToken, esAdministrador, asy
|
|||||||
app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador, async (req, res) => {
|
app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador, async (req, res) => {
|
||||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||||
|
const hora_inicio = req.body?.hora_inicio || req.query?.hora_inicio;
|
||||||
|
const hora_fin = req.body?.hora_fin || req.query?.hora_fin;
|
||||||
const estado = String(req.body?.estado_autorizacion || '')
|
const estado = String(req.body?.estado_autorizacion || '')
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
@ -5078,12 +5559,28 @@ app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador,
|
|||||||
return res.status(400).json({ error: 'estado_autorizacion invalido' });
|
return res.status(400).json({ error: 'estado_autorizacion invalido' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hora_inicio && !normalizeTimeInput(hora_inicio)) {
|
||||||
|
return res.status(400).json({ error: 'hora_inicio invalida' });
|
||||||
|
}
|
||||||
|
if (hora_fin && !normalizeTimeInput(hora_fin)) {
|
||||||
|
return res.status(400).json({ error: 'hora_fin invalida' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rango = buildFechaHoraRange(fecha_inicio, fecha_fin, hora_inicio, hora_fin);
|
||||||
|
if (!rango) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio o fecha_fin invalida' });
|
||||||
|
}
|
||||||
|
if (rango.inicio > rango.fin) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio no puede ser mayor que fecha_fin' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const params = [estado, fecha_inicio, fecha_fin];
|
const params = [estado, rango.inicio, rango.fin];
|
||||||
let sql = `
|
let sql = `
|
||||||
UPDATE autorizacion
|
UPDATE autorizacion
|
||||||
SET estado_autorizacion = $1
|
SET estado_autorizacion = $1
|
||||||
WHERE fecha_autorizacion BETWEEN $2::date AND $3::date
|
WHERE fecha_autorizacion BETWEEN ($2::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}')
|
||||||
|
AND ($3::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}')
|
||||||
`;
|
`;
|
||||||
|
|
||||||
if (soloPendientes) {
|
if (soloPendientes) {
|
||||||
@ -5111,6 +5608,8 @@ app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador,
|
|||||||
app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministrador, async (req, res) => {
|
app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministrador, async (req, res) => {
|
||||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||||
|
const hora_inicio = req.body?.hora_inicio || req.query?.hora_inicio;
|
||||||
|
const hora_fin = req.body?.hora_fin || req.query?.hora_fin;
|
||||||
const numeroDocumento =
|
const numeroDocumento =
|
||||||
req.body?.numero_documento_autorizante || req.query?.numero_documento_autorizante;
|
req.body?.numero_documento_autorizante || req.query?.numero_documento_autorizante;
|
||||||
const establecimiento = String(
|
const establecimiento = String(
|
||||||
@ -5135,6 +5634,21 @@ app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministra
|
|||||||
return res.status(400).json({ error: 'ambito invalido' });
|
return res.status(400).json({ error: 'ambito invalido' });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hora_inicio && !normalizeTimeInput(hora_inicio)) {
|
||||||
|
return res.status(400).json({ error: 'hora_inicio invalida' });
|
||||||
|
}
|
||||||
|
if (hora_fin && !normalizeTimeInput(hora_fin)) {
|
||||||
|
return res.status(400).json({ error: 'hora_fin invalida' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const rango = buildFechaHoraRange(fecha_inicio, fecha_fin, hora_inicio, hora_fin);
|
||||||
|
if (!rango) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio o fecha_fin invalida' });
|
||||||
|
}
|
||||||
|
if (rango.inicio > rango.fin) {
|
||||||
|
return res.status(400).json({ error: 'fecha_inicio no puede ser mayor que fecha_fin' });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const autorizanteRes = await pool.query(
|
const autorizanteRes = await pool.query(
|
||||||
'SELECT 1 FROM autorizante WHERE numero_documento = $1 LIMIT 1',
|
'SELECT 1 FROM autorizante WHERE numero_documento = $1 LIMIT 1',
|
||||||
@ -5144,8 +5658,10 @@ app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministra
|
|||||||
return res.status(404).json({ error: 'Autorizante no encontrado' });
|
return res.status(404).json({ error: 'Autorizante no encontrado' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const whereParts = ['a.fecha_autorizacion BETWEEN $2::date AND $3::date'];
|
const whereParts = [
|
||||||
const params = [numeroAutorizante, fecha_inicio, fecha_fin];
|
`a.fecha_autorizacion BETWEEN ($2::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}') AND ($3::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}')`,
|
||||||
|
];
|
||||||
|
const params = [numeroAutorizante, rango.inicio, rango.fin];
|
||||||
|
|
||||||
if (establecimiento) {
|
if (establecimiento) {
|
||||||
params.push(`%${establecimiento}%`);
|
params.push(`%${establecimiento}%`);
|
||||||
@ -5207,6 +5723,10 @@ ensureCupsTables().catch((error) => {
|
|||||||
console.error('Error inicializando tablas CUPS:', error.message);
|
console.error('Error inicializando tablas CUPS:', error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ensureCie10Table().catch((error) => {
|
||||||
|
console.error('Error inicializando tabla CIE-10:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
ensureAutorizacionVersionTables().catch((error) => {
|
ensureAutorizacionVersionTables().catch((error) => {
|
||||||
console.error('Error inicializando tablas de versiones:', error.message);
|
console.error('Error inicializando tablas de versiones:', error.message);
|
||||||
});
|
});
|
||||||
@ -5215,6 +5735,10 @@ ensureAutorizacionExtras().catch((error) => {
|
|||||||
console.error('Error inicializando columnas extra de autorizaciones:', error.message);
|
console.error('Error inicializando columnas extra de autorizaciones:', error.message);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ensureAutorizacionFechaHora().catch((error) => {
|
||||||
|
console.error('Error inicializando fecha/hora de autorizaciones:', error.message);
|
||||||
|
});
|
||||||
|
|
||||||
ensureIpsConvenio().catch((error) => {
|
ensureIpsConvenio().catch((error) => {
|
||||||
console.error('Error inicializando convenio de IPS:', error.message);
|
console.error('Error inicializando convenio de IPS:', error.message);
|
||||||
});
|
});
|
||||||
|
|||||||
@ -60,6 +60,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hora_inicio">Hora de inicio:</label>
|
||||||
|
<input
|
||||||
|
id="hora_inicio"
|
||||||
|
type="time"
|
||||||
|
formControlName="hora_inicio"
|
||||||
|
(change)="validarFechas()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="hora_fin">Hora de fin:</label>
|
||||||
|
<input
|
||||||
|
id="hora_fin"
|
||||||
|
type="time"
|
||||||
|
formControlName="hora_fin"
|
||||||
|
(change)="validarFechas()"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
|
|||||||
@ -47,6 +47,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
// Fechas en formato yyyy-MM-dd para usar en la API
|
// Fechas en formato yyyy-MM-dd para usar en la API
|
||||||
private fechaInicioApi: string | null = null;
|
private fechaInicioApi: string | null = null;
|
||||||
private fechaFinApi: string | null = null;
|
private fechaFinApi: string | null = null;
|
||||||
|
private horaInicioApi: string | null = null;
|
||||||
|
private horaFinApi: string | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
@ -59,6 +61,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
this.filtroForm = this.fb.group({
|
this.filtroForm = this.fb.group({
|
||||||
fecha_inicio: ['', Validators.required],
|
fecha_inicio: ['', Validators.required],
|
||||||
fecha_fin: ['', Validators.required],
|
fecha_fin: ['', Validators.required],
|
||||||
|
hora_inicio: [''],
|
||||||
|
hora_fin: [''],
|
||||||
establecimiento: [''],
|
establecimiento: [''],
|
||||||
ambito: [''],
|
ambito: [''],
|
||||||
ips: [''],
|
ips: [''],
|
||||||
@ -81,7 +85,9 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
|
|
||||||
this.filtroForm.patchValue({
|
this.filtroForm.patchValue({
|
||||||
fecha_inicio: this.formatDateForInput(hace30Dias),
|
fecha_inicio: this.formatDateForInput(hace30Dias),
|
||||||
fecha_fin: this.formatDateForInput(hoy)
|
fecha_fin: this.formatDateForInput(hoy),
|
||||||
|
hora_inicio: '00:00',
|
||||||
|
hora_fin: '23:59'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,17 +100,39 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const inicio = new Date(this.fecha_inicio.value);
|
const fechaInicio = this.normalizeDateInput(this.fecha_inicio.value);
|
||||||
const fin = new Date(this.fecha_fin.value);
|
const fechaFin = this.normalizeDateInput(this.fecha_fin.value);
|
||||||
|
if (!fechaInicio || !fechaFin) {
|
||||||
|
this.errorMessage = 'Debes ingresar fechas validas.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (inicio > fin) {
|
const horaInicioRaw = this.filtroForm.get('hora_inicio')?.value;
|
||||||
|
const horaFinRaw = this.filtroForm.get('hora_fin')?.value;
|
||||||
|
const horaInicio = this.normalizeTimeInput(horaInicioRaw) || '00:00';
|
||||||
|
const horaFin = this.normalizeTimeInput(horaFinRaw) || '23:59';
|
||||||
|
|
||||||
|
if (horaInicioRaw && !this.normalizeTimeInput(horaInicioRaw)) {
|
||||||
|
this.errorMessage = 'La hora de inicio es invalida.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (horaFinRaw && !this.normalizeTimeInput(horaFinRaw)) {
|
||||||
|
this.errorMessage = 'La hora de fin es invalida.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const inicioStr = `${fechaInicio} ${horaInicio}`;
|
||||||
|
const finStr = `${fechaFin} ${horaFin}`;
|
||||||
|
if (inicioStr > finStr) {
|
||||||
this.errorMessage = 'La fecha de inicio no puede ser mayor que la fecha de fin.';
|
this.errorMessage = 'La fecha de inicio no puede ser mayor que la fecha de fin.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Guardamos las fechas ya formateadas para reutilizarlas
|
// Guardamos las fechas ya formateadas para reutilizarlas
|
||||||
this.fechaInicioApi = this.formatDateForInput(inicio);
|
this.fechaInicioApi = fechaInicio;
|
||||||
this.fechaFinApi = this.formatDateForInput(fin);
|
this.fechaFinApi = fechaFin;
|
||||||
|
this.horaInicioApi = horaInicio;
|
||||||
|
this.horaFinApi = horaFin;
|
||||||
this.establecimientoFiltro = String(this.filtroForm.get('establecimiento')?.value || '').trim();
|
this.establecimientoFiltro = String(this.filtroForm.get('establecimiento')?.value || '').trim();
|
||||||
this.ambitoFiltro = String(this.filtroForm.get('ambito')?.value || '').trim().toLowerCase();
|
this.ambitoFiltro = String(this.filtroForm.get('ambito')?.value || '').trim().toLowerCase();
|
||||||
this.ipsFiltro = String(this.filtroForm.get('ips')?.value || '').trim();
|
this.ipsFiltro = String(this.filtroForm.get('ips')?.value || '').trim();
|
||||||
@ -118,6 +146,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
.getAutorizacionesPorFecha(
|
.getAutorizacionesPorFecha(
|
||||||
this.fechaInicioApi!,
|
this.fechaInicioApi!,
|
||||||
this.fechaFinApi!,
|
this.fechaFinApi!,
|
||||||
|
this.horaInicioApi || undefined,
|
||||||
|
this.horaFinApi || undefined,
|
||||||
this.limiteAutorizaciones,
|
this.limiteAutorizaciones,
|
||||||
0,
|
0,
|
||||||
this.establecimientoFiltro || undefined,
|
this.establecimientoFiltro || undefined,
|
||||||
@ -286,6 +316,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
.actualizarEstadoAutorizacionesMasivo(
|
.actualizarEstadoAutorizacionesMasivo(
|
||||||
this.fechaInicioApi,
|
this.fechaInicioApi,
|
||||||
this.fechaFinApi,
|
this.fechaFinApi,
|
||||||
|
this.horaInicioApi || undefined,
|
||||||
|
this.horaFinApi || undefined,
|
||||||
this.estadoMasivo
|
this.estadoMasivo
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
@ -348,6 +380,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
.actualizarAutorizanteMasivo(
|
.actualizarAutorizanteMasivo(
|
||||||
this.fechaInicioApi,
|
this.fechaInicioApi,
|
||||||
this.fechaFinApi,
|
this.fechaFinApi,
|
||||||
|
this.horaInicioApi || undefined,
|
||||||
|
this.horaFinApi || undefined,
|
||||||
autorizadoId,
|
autorizadoId,
|
||||||
this.establecimientoFiltro || undefined,
|
this.establecimientoFiltro || undefined,
|
||||||
this.ambitoFiltro || undefined,
|
this.ambitoFiltro || undefined,
|
||||||
@ -466,6 +500,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
.crearJobZipAutorizaciones(
|
.crearJobZipAutorizaciones(
|
||||||
this.fechaInicioApi,
|
this.fechaInicioApi,
|
||||||
this.fechaFinApi,
|
this.fechaFinApi,
|
||||||
|
this.horaInicioApi || undefined,
|
||||||
|
this.horaFinApi || undefined,
|
||||||
this.establecimientoFiltro || undefined,
|
this.establecimientoFiltro || undefined,
|
||||||
this.ambitoFiltro || undefined,
|
this.ambitoFiltro || undefined,
|
||||||
this.ipsFiltro || undefined
|
this.ipsFiltro || undefined
|
||||||
@ -475,9 +511,15 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
this.jobsService.pollJob(job.id).subscribe({
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
next: (estado) => {
|
next: (estado) => {
|
||||||
if (estado.status === 'completed') {
|
if (estado.status === 'completed') {
|
||||||
|
const inicioLabel = this.horaInicioApi
|
||||||
|
? `${this.fechaInicioApi}_${this.horaInicioApi.replace(':', '')}`
|
||||||
|
: this.fechaInicioApi;
|
||||||
|
const finLabel = this.horaFinApi
|
||||||
|
? `${this.fechaFinApi}_${this.horaFinApi.replace(':', '')}`
|
||||||
|
: this.fechaFinApi;
|
||||||
const fileName =
|
const fileName =
|
||||||
estado.result?.fileName ||
|
estado.result?.fileName ||
|
||||||
`autorizaciones_${this.fechaInicioApi}_${this.fechaFinApi}.zip`;
|
`autorizaciones_${inicioLabel}_${finLabel}.zip`;
|
||||||
|
|
||||||
this.jobsService.downloadJobFile(job.id).subscribe({
|
this.jobsService.downloadJobFile(job.id).subscribe({
|
||||||
next: (blob) => {
|
next: (blob) => {
|
||||||
@ -536,8 +578,59 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
return `${year}-${month}-${day}`;
|
return `${year}-${month}-${day}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private normalizeDateInput(value: string): string {
|
||||||
|
const raw = String(value || '').trim();
|
||||||
|
if (!/^\d{4}-\d{2}-\d{2}$/.test(raw)) return '';
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
private normalizeTimeInput(value: string): string {
|
||||||
|
const raw = String(value || '').trim();
|
||||||
|
if (!raw) return '';
|
||||||
|
const match = raw.match(/^(\d{2}):(\d{2})$/);
|
||||||
|
if (!match) return '';
|
||||||
|
const hour = Number(match[1]);
|
||||||
|
const minute = Number(match[2]);
|
||||||
|
if (
|
||||||
|
!Number.isFinite(hour) ||
|
||||||
|
!Number.isFinite(minute) ||
|
||||||
|
hour < 0 ||
|
||||||
|
hour > 23 ||
|
||||||
|
minute < 0 ||
|
||||||
|
minute > 59
|
||||||
|
) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${match[1]}:${match[2]}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private parseLocalDateTime(value: string): Date | null {
|
||||||
|
if (!value) return null;
|
||||||
|
const raw = String(value).trim();
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
||||||
|
const [year, month, day] = raw.split('-').map(Number);
|
||||||
|
return new Date(year, month - 1, day);
|
||||||
|
}
|
||||||
|
const parsed = new Date(raw);
|
||||||
|
if (!Number.isNaN(parsed.getTime())) {
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
const match = raw.match(
|
||||||
|
/^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?/
|
||||||
|
);
|
||||||
|
if (!match) return null;
|
||||||
|
const year = Number(match[1]);
|
||||||
|
const month = Number(match[2]);
|
||||||
|
const day = Number(match[3]);
|
||||||
|
const hour = Number(match[4]);
|
||||||
|
const minute = Number(match[5]);
|
||||||
|
const second = Number(match[6] || '0');
|
||||||
|
return new Date(year, month - 1, day, hour, minute, second);
|
||||||
|
}
|
||||||
|
|
||||||
formatDate(dateString: string): string {
|
formatDate(dateString: string): string {
|
||||||
const date = new Date(dateString);
|
const date = this.parseLocalDateTime(dateString);
|
||||||
|
if (!date) return '';
|
||||||
return date.toLocaleDateString('es-ES', {
|
return date.toLocaleDateString('es-ES', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@ -546,7 +639,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
formatDateTime(dateString: string): string {
|
formatDateTime(dateString: string): string {
|
||||||
const date = new Date(dateString);
|
const date = this.parseLocalDateTime(dateString);
|
||||||
|
if (!date) return '';
|
||||||
return date.toLocaleDateString('es-ES', {
|
return date.toLocaleDateString('es-ES', {
|
||||||
year: 'numeric',
|
year: 'numeric',
|
||||||
month: 'short',
|
month: 'short',
|
||||||
@ -709,14 +803,33 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
validarFechas(): void {
|
validarFechas(): void {
|
||||||
const fechaInicio = this.filtroForm.get('fecha_inicio')?.value;
|
const fechaInicioRaw = this.filtroForm.get('fecha_inicio')?.value;
|
||||||
const fechaFin = this.filtroForm.get('fecha_fin')?.value;
|
const fechaFinRaw = this.filtroForm.get('fecha_fin')?.value;
|
||||||
|
const fechaInicio = this.normalizeDateInput(fechaInicioRaw);
|
||||||
|
const fechaFin = this.normalizeDateInput(fechaFinRaw);
|
||||||
|
const horaInicioRaw = this.filtroForm.get('hora_inicio')?.value;
|
||||||
|
const horaFinRaw = this.filtroForm.get('hora_fin')?.value;
|
||||||
|
const horaInicio = this.normalizeTimeInput(horaInicioRaw) || '00:00';
|
||||||
|
const horaFin = this.normalizeTimeInput(horaFinRaw) || '23:59';
|
||||||
|
|
||||||
if (fechaInicio && fechaFin && new Date(fechaFin) < new Date(fechaInicio)) {
|
if (
|
||||||
|
(horaInicioRaw && !this.normalizeTimeInput(horaInicioRaw)) ||
|
||||||
|
(horaFinRaw && !this.normalizeTimeInput(horaFinRaw))
|
||||||
|
) {
|
||||||
this.filtroForm.get('fecha_fin')?.setErrors({ fechaInvalida: true });
|
this.filtroForm.get('fecha_fin')?.setErrors({ fechaInvalida: true });
|
||||||
} else {
|
return;
|
||||||
this.filtroForm.get('fecha_fin')?.setErrors(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fechaInicio && fechaFin) {
|
||||||
|
const inicioStr = `${fechaInicio} ${horaInicio}`;
|
||||||
|
const finStr = `${fechaFin} ${horaFin}`;
|
||||||
|
if (inicioStr > finStr) {
|
||||||
|
this.filtroForm.get('fecha_fin')?.setErrors({ fechaInvalida: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.filtroForm.get('fecha_fin')?.setErrors(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========= EXPORTAR A CSV SIMPLE =========
|
// ========= EXPORTAR A CSV SIMPLE =========
|
||||||
|
|||||||
@ -72,6 +72,17 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cie10-row .cie10-input {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cie10-row .cie10-input input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.cup-results {
|
.cup-results {
|
||||||
margin-bottom: 12px;
|
margin-bottom: 12px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -377,14 +377,30 @@
|
|||||||
{{ errorCups }}
|
{{ errorCups }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row cie10-row">
|
||||||
<label for="cie10Codigo">Codigo CIE-10:</label>
|
<label for="cie10Codigo">Codigo CIE-10:</label>
|
||||||
|
<div class="cie10-input">
|
||||||
<input
|
<input
|
||||||
id="cie10Codigo"
|
id="cie10Codigo"
|
||||||
type="text"
|
type="text"
|
||||||
[(ngModel)]="formAutorizacion.cie10_codigo"
|
[(ngModel)]="formAutorizacion.cie10_codigo"
|
||||||
placeholder="Ej: A00"
|
placeholder="Ej: A00"
|
||||||
|
(keyup.enter)="buscarCie10()"
|
||||||
|
(input)="onCie10InputChange()"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="btn btn-secondary btn-sm"
|
||||||
|
(click)="buscarCie10()"
|
||||||
|
[disabled]="buscandoCie10"
|
||||||
|
>
|
||||||
|
{{ buscandoCie10 ? 'Buscando...' : 'Buscar' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status error" *ngIf="errorCie10">
|
||||||
|
{{ errorCie10 }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
@ -404,6 +420,15 @@
|
|||||||
[(ngModel)]="formAutorizacion.fecha_autorizacion"
|
[(ngModel)]="formAutorizacion.fecha_autorizacion"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="horaAut">Hora autorizaci?n:</label>
|
||||||
|
<input
|
||||||
|
id="horaAut"
|
||||||
|
type="time"
|
||||||
|
[(ngModel)]="formAutorizacion.hora_autorizacion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<label for="obs">Observación / servicios autorizados:</label>
|
<label for="obs">Observación / servicios autorizados:</label>
|
||||||
|
|||||||
@ -35,6 +35,9 @@ export class AutorizacionesComponent {
|
|||||||
cupSeleccionado: CupInfo | null = null;
|
cupSeleccionado: CupInfo | null = null;
|
||||||
buscandoCups = false;
|
buscandoCups = false;
|
||||||
errorCups: string | null = null;
|
errorCups: string | null = null;
|
||||||
|
buscandoCie10 = false;
|
||||||
|
errorCie10: string | null = null;
|
||||||
|
private cie10Timer: ReturnType<typeof setTimeout> | null = null;
|
||||||
verMasIps = false;
|
verMasIps = false;
|
||||||
departamentoInterno = '';
|
departamentoInterno = '';
|
||||||
observacionTraslado = '';
|
observacionTraslado = '';
|
||||||
@ -50,6 +53,7 @@ export class AutorizacionesComponent {
|
|||||||
id_ips: '',
|
id_ips: '',
|
||||||
numero_documento_autorizante: '',
|
numero_documento_autorizante: '',
|
||||||
fecha_autorizacion: '',
|
fecha_autorizacion: '',
|
||||||
|
hora_autorizacion: '',
|
||||||
observacion: '',
|
observacion: '',
|
||||||
cup_codigo: '',
|
cup_codigo: '',
|
||||||
cie10_codigo: '',
|
cie10_codigo: '',
|
||||||
@ -146,6 +150,7 @@ export class AutorizacionesComponent {
|
|||||||
id_ips: '',
|
id_ips: '',
|
||||||
numero_documento_autorizante: '',
|
numero_documento_autorizante: '',
|
||||||
fecha_autorizacion: '',
|
fecha_autorizacion: '',
|
||||||
|
hora_autorizacion: '',
|
||||||
observacion: '',
|
observacion: '',
|
||||||
cup_codigo: '',
|
cup_codigo: '',
|
||||||
cie10_codigo: '',
|
cie10_codigo: '',
|
||||||
@ -155,6 +160,7 @@ export class AutorizacionesComponent {
|
|||||||
ambito_atencion: '',
|
ambito_atencion: '',
|
||||||
numero_orden: '',
|
numero_orden: '',
|
||||||
};
|
};
|
||||||
|
this.setFechaHoraActual();
|
||||||
|
|
||||||
this.autorizacionesPaciente = [];
|
this.autorizacionesPaciente = [];
|
||||||
this.versionesPorAutorizacion = {};
|
this.versionesPorAutorizacion = {};
|
||||||
@ -165,6 +171,11 @@ export class AutorizacionesComponent {
|
|||||||
this.cupsDisponibles = [];
|
this.cupsDisponibles = [];
|
||||||
this.cupSeleccionado = null;
|
this.cupSeleccionado = null;
|
||||||
this.errorCups = null;
|
this.errorCups = null;
|
||||||
|
this.errorCie10 = null;
|
||||||
|
if (this.cie10Timer) {
|
||||||
|
clearTimeout(this.cie10Timer);
|
||||||
|
this.cie10Timer = null;
|
||||||
|
}
|
||||||
|
|
||||||
this.cargarIps(p.interno, this.verMasIps);
|
this.cargarIps(p.interno, this.verMasIps);
|
||||||
this.cargarAutorizantes();
|
this.cargarAutorizantes();
|
||||||
@ -180,6 +191,11 @@ export class AutorizacionesComponent {
|
|||||||
this.cupsDisponibles = [];
|
this.cupsDisponibles = [];
|
||||||
this.cupSeleccionado = null;
|
this.cupSeleccionado = null;
|
||||||
this.errorCups = null;
|
this.errorCups = null;
|
||||||
|
this.errorCie10 = null;
|
||||||
|
if (this.cie10Timer) {
|
||||||
|
clearTimeout(this.cie10Timer);
|
||||||
|
this.cie10Timer = null;
|
||||||
|
}
|
||||||
this.verMasIps = false;
|
this.verMasIps = false;
|
||||||
this.departamentoInterno = '';
|
this.departamentoInterno = '';
|
||||||
this.observacionTraslado = '';
|
this.observacionTraslado = '';
|
||||||
@ -332,6 +348,63 @@ export class AutorizacionesComponent {
|
|||||||
this.cupSeleccionado = null;
|
this.cupSeleccionado = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buscarCie10(): void {
|
||||||
|
const codigo = String(this.formAutorizacion.cie10_codigo || '')
|
||||||
|
.trim()
|
||||||
|
.toUpperCase();
|
||||||
|
if (!codigo) {
|
||||||
|
this.errorCie10 = 'Ingresa un codigo CIE-10 para buscar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.formAutorizacion.cie10_codigo = codigo;
|
||||||
|
this.buscandoCie10 = true;
|
||||||
|
this.errorCie10 = null;
|
||||||
|
|
||||||
|
this.pacienteService
|
||||||
|
.buscarCie10PorCodigo(codigo)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.buscandoCie10 = false;
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (data) => {
|
||||||
|
if (data?.descripcion) {
|
||||||
|
this.formAutorizacion.cie10_descripcion = data.descripcion;
|
||||||
|
} else {
|
||||||
|
this.errorCie10 = 'No se encontro el diagnostico CIE-10.';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorCie10 =
|
||||||
|
err?.status === 404
|
||||||
|
? 'No se encontro el diagnostico CIE-10.'
|
||||||
|
: 'Error consultando CIE-10.';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCie10InputChange(): void {
|
||||||
|
this.errorCie10 = null;
|
||||||
|
this.formAutorizacion.cie10_descripcion = '';
|
||||||
|
|
||||||
|
if (this.cie10Timer) {
|
||||||
|
clearTimeout(this.cie10Timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
const codigo = String(this.formAutorizacion.cie10_codigo || '').trim();
|
||||||
|
if (!codigo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cie10Timer = setTimeout(() => {
|
||||||
|
this.buscarCie10();
|
||||||
|
}, 400);
|
||||||
|
}
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// Guardar autorización
|
// Guardar autorización
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@ -459,6 +532,9 @@ export class AutorizacionesComponent {
|
|||||||
if (resp?.fecha_ingreso_urgencias) {
|
if (resp?.fecha_ingreso_urgencias) {
|
||||||
observacionExtras.push(`Ingreso a urgencias: ${resp.fecha_ingreso_urgencias}`);
|
observacionExtras.push(`Ingreso a urgencias: ${resp.fecha_ingreso_urgencias}`);
|
||||||
}
|
}
|
||||||
|
if (resp?.fecha_egreso) {
|
||||||
|
observacionExtras.push(`Fecha de egreso: ${resp.fecha_egreso}`);
|
||||||
|
}
|
||||||
if (observacionExtras.length) {
|
if (observacionExtras.length) {
|
||||||
const actual = String(this.formAutorizacion.observacion || '').trim();
|
const actual = String(this.formAutorizacion.observacion || '').trim();
|
||||||
const nuevos = observacionExtras.filter(
|
const nuevos = observacionExtras.filter(
|
||||||
@ -705,7 +781,42 @@ export class AutorizacionesComponent {
|
|||||||
|
|
||||||
private formatDateInput(value: string | null | undefined): string {
|
private formatDateInput(value: string | null | undefined): string {
|
||||||
if (!value) return '';
|
if (!value) return '';
|
||||||
return value.substring(0, 10);
|
const raw = String(value).trim();
|
||||||
|
if (/^\d{4}-\d{2}-\d{2}$/.test(raw)) {
|
||||||
|
const [year, month, day] = raw.split('-').map(Number);
|
||||||
|
const dateOnly = new Date(year, month - 1, day);
|
||||||
|
return this.formatDateInputFromDate(dateOnly);
|
||||||
|
}
|
||||||
|
const date = new Date(raw);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
return this.formatDateInputFromDate(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatDateInputFromDate(date: Date): string {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
const day = String(date.getDate()).padStart(2, '0');
|
||||||
|
return `${year}-${month}-${day}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatTimeInput(value: string | null | undefined): string {
|
||||||
|
if (!value) return '';
|
||||||
|
const raw = String(value).trim();
|
||||||
|
if (/^\d{2}:\d{2}/.test(raw) && !raw.includes('T')) {
|
||||||
|
return raw.substring(0, 5);
|
||||||
|
}
|
||||||
|
const date = new Date(raw);
|
||||||
|
if (Number.isNaN(date.getTime())) return '';
|
||||||
|
const hours = String(date.getHours()).padStart(2, '0');
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, '0');
|
||||||
|
return `${hours}:${minutes}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setFechaHoraActual(): void {
|
||||||
|
const ahora = new Date();
|
||||||
|
this.formAutorizacion.fecha_autorizacion =
|
||||||
|
this.formatDateInputFromDate(ahora);
|
||||||
|
this.formAutorizacion.hora_autorizacion = this.formatTimeInput(ahora.toISOString());
|
||||||
}
|
}
|
||||||
|
|
||||||
iniciarEdicion(autorizacion: any): void {
|
iniciarEdicion(autorizacion: any): void {
|
||||||
@ -714,6 +825,7 @@ export class AutorizacionesComponent {
|
|||||||
this.autorizacionEditando = autorizacion;
|
this.autorizacionEditando = autorizacion;
|
||||||
this.mensajeAutorizacion = null;
|
this.mensajeAutorizacion = null;
|
||||||
this.errorAutorizacion = null;
|
this.errorAutorizacion = null;
|
||||||
|
this.errorCie10 = null;
|
||||||
|
|
||||||
this.formAutorizacion = {
|
this.formAutorizacion = {
|
||||||
id_ips: String(autorizacion.id_ips || ''),
|
id_ips: String(autorizacion.id_ips || ''),
|
||||||
@ -721,6 +833,7 @@ export class AutorizacionesComponent {
|
|||||||
autorizacion.numero_documento_autorizante || ''
|
autorizacion.numero_documento_autorizante || ''
|
||||||
),
|
),
|
||||||
fecha_autorizacion: this.formatDateInput(autorizacion.fecha_autorizacion),
|
fecha_autorizacion: this.formatDateInput(autorizacion.fecha_autorizacion),
|
||||||
|
hora_autorizacion: this.formatTimeInput(autorizacion.fecha_autorizacion),
|
||||||
observacion: autorizacion.observacion || '',
|
observacion: autorizacion.observacion || '',
|
||||||
cup_codigo: autorizacion.cup_codigo || '',
|
cup_codigo: autorizacion.cup_codigo || '',
|
||||||
cie10_codigo: autorizacion.cie10_codigo || '',
|
cie10_codigo: autorizacion.cie10_codigo || '',
|
||||||
@ -745,10 +858,16 @@ export class AutorizacionesComponent {
|
|||||||
this.autorizacionEditando = null;
|
this.autorizacionEditando = null;
|
||||||
this.observacionTraslado = '';
|
this.observacionTraslado = '';
|
||||||
this.cupSeleccionado = null;
|
this.cupSeleccionado = null;
|
||||||
|
this.errorCie10 = null;
|
||||||
|
if (this.cie10Timer) {
|
||||||
|
clearTimeout(this.cie10Timer);
|
||||||
|
this.cie10Timer = null;
|
||||||
|
}
|
||||||
this.formAutorizacion = {
|
this.formAutorizacion = {
|
||||||
id_ips: '',
|
id_ips: '',
|
||||||
numero_documento_autorizante: '',
|
numero_documento_autorizante: '',
|
||||||
fecha_autorizacion: '',
|
fecha_autorizacion: '',
|
||||||
|
hora_autorizacion: '',
|
||||||
observacion: '',
|
observacion: '',
|
||||||
cup_codigo: '',
|
cup_codigo: '',
|
||||||
cie10_codigo: '',
|
cie10_codigo: '',
|
||||||
@ -758,6 +877,7 @@ export class AutorizacionesComponent {
|
|||||||
ambito_atencion: '',
|
ambito_atencion: '',
|
||||||
numero_orden: '',
|
numero_orden: '',
|
||||||
};
|
};
|
||||||
|
this.setFechaHoraActual();
|
||||||
this.limpiarArchivosHospitalarios();
|
this.limpiarArchivosHospitalarios();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -785,6 +905,14 @@ export class AutorizacionesComponent {
|
|||||||
this.errorAutorizacion = 'Debe seleccionar quién autoriza.';
|
this.errorAutorizacion = 'Debe seleccionar quién autoriza.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!this.formAutorizacion.fecha_autorizacion) {
|
||||||
|
this.errorAutorizacion = 'Debe seleccionar la fecha de autorizacion.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.formAutorizacion.hora_autorizacion) {
|
||||||
|
this.errorAutorizacion = 'Debe seleccionar la hora de autorizacion.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (!this.formAutorizacion.cup_codigo) {
|
if (!this.formAutorizacion.cup_codigo) {
|
||||||
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
||||||
return;
|
return;
|
||||||
@ -847,6 +975,8 @@ export class AutorizacionesComponent {
|
|||||||
observacion: this.buildObservacionFinal(),
|
observacion: this.buildObservacionFinal(),
|
||||||
fecha_autorizacion:
|
fecha_autorizacion:
|
||||||
this.formAutorizacion.fecha_autorizacion || undefined,
|
this.formAutorizacion.fecha_autorizacion || undefined,
|
||||||
|
hora_autorizacion:
|
||||||
|
this.formAutorizacion.hora_autorizacion || undefined,
|
||||||
cup_codigo: this.formAutorizacion.cup_codigo,
|
cup_codigo: this.formAutorizacion.cup_codigo,
|
||||||
cie10_codigo: this.formAutorizacion.cie10_codigo,
|
cie10_codigo: this.formAutorizacion.cie10_codigo,
|
||||||
cie10_descripcion: this.formAutorizacion.cie10_descripcion,
|
cie10_descripcion: this.formAutorizacion.cie10_descripcion,
|
||||||
|
|||||||
@ -45,5 +45,31 @@
|
|||||||
<div class="status ok" *ngIf="statusMessage">{{ statusMessage }}</div>
|
<div class="status ok" *ngIf="statusMessage">{{ statusMessage }}</div>
|
||||||
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="card cups-card">
|
||||||
|
<h2>Cargar CIE-10</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
Sube el archivo con el codigo y el diagnostico CIE-10.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Archivo CIE-10 (Excel):</label>
|
||||||
|
<input type="file" accept=".xlsx,.xls" (change)="onCie10Selected($event)" />
|
||||||
|
<span class="file-name" *ngIf="cie10File">{{ cie10File.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row acciones">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="procesarCie10()"
|
||||||
|
[disabled]="cie10Loading || !cie10File"
|
||||||
|
>
|
||||||
|
{{ cie10Loading ? 'Procesando...' : 'Procesar CIE-10' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status ok" *ngIf="cie10StatusMessage">{{ cie10StatusMessage }}</div>
|
||||||
|
<div class="status error" *ngIf="cie10ErrorMessage">{{ cie10ErrorMessage }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -16,9 +16,13 @@ import { JobsService } from '../../services/jobs';
|
|||||||
export class CargarCupsComponent implements OnInit {
|
export class CargarCupsComponent implements OnInit {
|
||||||
notaFile: File | null = null;
|
notaFile: File | null = null;
|
||||||
referenciaFile: File | null = null;
|
referenciaFile: File | null = null;
|
||||||
|
cie10File: File | null = null;
|
||||||
isLoading = false;
|
isLoading = false;
|
||||||
|
cie10Loading = false;
|
||||||
statusMessage: string | null = null;
|
statusMessage: string | null = null;
|
||||||
errorMessage: string | null = null;
|
errorMessage: string | null = null;
|
||||||
|
cie10StatusMessage: string | null = null;
|
||||||
|
cie10ErrorMessage: string | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
@ -62,6 +66,12 @@ export class CargarCupsComponent implements OnInit {
|
|||||||
this.limpiarMensajes();
|
this.limpiarMensajes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onCie10Selected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
this.cie10File = input.files?.[0] || null;
|
||||||
|
this.limpiarMensajesCie10();
|
||||||
|
}
|
||||||
|
|
||||||
procesarCups(): void {
|
procesarCups(): void {
|
||||||
this.limpiarMensajes();
|
this.limpiarMensajes();
|
||||||
|
|
||||||
@ -128,8 +138,81 @@ export class CargarCupsComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
procesarCie10(): void {
|
||||||
|
this.limpiarMensajesCie10();
|
||||||
|
|
||||||
|
if (!this.cie10File) {
|
||||||
|
this.cie10ErrorMessage = 'Debes seleccionar el archivo de CIE-10.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cie10Loading = true;
|
||||||
|
this.cie10StatusMessage = 'Subiendo archivo...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('archivo', this.cie10File);
|
||||||
|
|
||||||
|
this.pacienteService.cargarExcelCie10(formData).subscribe({
|
||||||
|
next: (job) => {
|
||||||
|
this.cie10StatusMessage = 'Archivo en cola. Procesando...';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
|
||||||
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
|
next: (estado) => {
|
||||||
|
if (estado.status === 'completed') {
|
||||||
|
const partes: string[] = [];
|
||||||
|
if (estado.result?.mensaje) {
|
||||||
|
partes.push(estado.result.mensaje);
|
||||||
|
}
|
||||||
|
if (typeof estado.result?.procesadas === 'number') {
|
||||||
|
partes.push(`Procesadas: ${estado.result.procesadas}`);
|
||||||
|
}
|
||||||
|
if (typeof estado.result?.omitidas === 'number') {
|
||||||
|
partes.push(`Omitidas: ${estado.result.omitidas}`);
|
||||||
|
}
|
||||||
|
if (typeof estado.result?.total === 'number') {
|
||||||
|
partes.push(`Total: ${estado.result.total}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cie10StatusMessage =
|
||||||
|
partes.join(' - ') || 'CIE-10 procesado correctamente.';
|
||||||
|
this.cie10Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estado.status === 'failed') {
|
||||||
|
this.cie10ErrorMessage =
|
||||||
|
estado.error?.message || 'Error procesando el archivo de CIE-10.';
|
||||||
|
this.cie10Loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.cie10ErrorMessage =
|
||||||
|
'Error consultando el estado del procesamiento de CIE-10.';
|
||||||
|
this.cie10Loading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.cie10ErrorMessage =
|
||||||
|
err?.error?.error || 'Error subiendo el archivo de CIE-10.';
|
||||||
|
this.cie10Loading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private limpiarMensajes(): void {
|
private limpiarMensajes(): void {
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
this.statusMessage = null;
|
this.statusMessage = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private limpiarMensajesCie10(): void {
|
||||||
|
this.cie10ErrorMessage = null;
|
||||||
|
this.cie10StatusMessage = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -177,7 +177,7 @@ export class EstadisticasAutorizacionesComponent implements OnInit {
|
|||||||
this.isLoadingDetalle = true;
|
this.isLoadingDetalle = true;
|
||||||
|
|
||||||
this.authService
|
this.authService
|
||||||
.getAutorizacionesPorFecha(bucket.inicio, bucket.fin, 500, 0)
|
.getAutorizacionesPorFecha(bucket.inicio, bucket.fin, undefined, undefined, 500, 0)
|
||||||
.pipe(
|
.pipe(
|
||||||
finalize(() => {
|
finalize(() => {
|
||||||
this.isLoadingDetalle = false;
|
this.isLoadingDetalle = false;
|
||||||
|
|||||||
@ -312,6 +312,8 @@ export class AuthService {
|
|||||||
getAutorizacionesPorFecha(
|
getAutorizacionesPorFecha(
|
||||||
fechaInicio: string,
|
fechaInicio: string,
|
||||||
fechaFin: string,
|
fechaFin: string,
|
||||||
|
horaInicio?: string,
|
||||||
|
horaFin?: string,
|
||||||
limit = 100000,
|
limit = 100000,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
establecimiento?: string,
|
establecimiento?: string,
|
||||||
@ -320,6 +322,12 @@ export class AuthService {
|
|||||||
): Observable<any[]> {
|
): Observable<any[]> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
const params: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset };
|
const params: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset };
|
||||||
|
if (horaInicio) {
|
||||||
|
params.hora_inicio = horaInicio;
|
||||||
|
}
|
||||||
|
if (horaFin) {
|
||||||
|
params.hora_fin = horaFin;
|
||||||
|
}
|
||||||
if (establecimiento) {
|
if (establecimiento) {
|
||||||
params.establecimiento = establecimiento;
|
params.establecimiento = establecimiento;
|
||||||
}
|
}
|
||||||
@ -371,12 +379,20 @@ export class AuthService {
|
|||||||
crearJobZipAutorizaciones(
|
crearJobZipAutorizaciones(
|
||||||
fechaInicio: string,
|
fechaInicio: string,
|
||||||
fechaFin: string,
|
fechaFin: string,
|
||||||
|
horaInicio?: string,
|
||||||
|
horaFin?: string,
|
||||||
establecimiento?: string,
|
establecimiento?: string,
|
||||||
ambito?: string,
|
ambito?: string,
|
||||||
ips?: string
|
ips?: string
|
||||||
): Observable<JobResponse> {
|
): Observable<JobResponse> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
const payload: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin };
|
const payload: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin };
|
||||||
|
if (horaInicio) {
|
||||||
|
payload.hora_inicio = horaInicio;
|
||||||
|
}
|
||||||
|
if (horaFin) {
|
||||||
|
payload.hora_fin = horaFin;
|
||||||
|
}
|
||||||
if (establecimiento) {
|
if (establecimiento) {
|
||||||
payload.establecimiento = establecimiento;
|
payload.establecimiento = establecimiento;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ export interface JobResult {
|
|||||||
actualizados?: number | null;
|
actualizados?: number | null;
|
||||||
omitidos?: number | null;
|
omitidos?: number | null;
|
||||||
omitidas?: number | null;
|
omitidas?: number | null;
|
||||||
|
procesadas?: number | null;
|
||||||
desactivados?: number | null;
|
desactivados?: number | null;
|
||||||
duplicados?: number | null;
|
duplicados?: number | null;
|
||||||
creadas?: number | null;
|
creadas?: number | null;
|
||||||
|
|||||||
@ -76,6 +76,7 @@ export interface CrearAutorizacionPayload {
|
|||||||
estado_entrega?: string;
|
estado_entrega?: string;
|
||||||
observacion?: string;
|
observacion?: string;
|
||||||
fecha_autorizacion?: string; // yyyy-MM-dd
|
fecha_autorizacion?: string; // yyyy-MM-dd
|
||||||
|
hora_autorizacion?: string; // HH:mm
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RespuestaAutorizacion {
|
export interface RespuestaAutorizacion {
|
||||||
@ -136,6 +137,11 @@ export interface CupInfo {
|
|||||||
cubierto: boolean;
|
cubierto: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface Cie10Info {
|
||||||
|
codigo: string;
|
||||||
|
descripcion: string;
|
||||||
|
}
|
||||||
|
|
||||||
// ====== Servicio ======
|
// ====== Servicio ======
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -328,6 +334,19 @@ export class PacienteService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cargarExcelCie10(formData: FormData): Observable<JobResponse> {
|
||||||
|
const token = this.authService.getToken();
|
||||||
|
const headers = new HttpHeaders({
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.http.post<JobResponse>(
|
||||||
|
`${this.API_URL}/cargar-cie10`,
|
||||||
|
formData,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
cargarIps(formData: FormData): Observable<JobResponse> {
|
cargarIps(formData: FormData): Observable<JobResponse> {
|
||||||
const token = this.authService.getToken();
|
const token = this.authService.getToken();
|
||||||
const headers = new HttpHeaders({
|
const headers = new HttpHeaders({
|
||||||
@ -385,6 +404,14 @@ export class PacienteService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buscarCie10PorCodigo(codigo: string): Observable<Cie10Info> {
|
||||||
|
const params = new HttpParams().set('codigo', codigo);
|
||||||
|
return this.http.get<Cie10Info>(`${this.API_URL}/cie10`, {
|
||||||
|
params,
|
||||||
|
headers: this.getAuthHeaders()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
actualizarEstadoAutorizacion(
|
actualizarEstadoAutorizacion(
|
||||||
numeroAutorizacion: string,
|
numeroAutorizacion: string,
|
||||||
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
||||||
@ -407,6 +434,8 @@ export class PacienteService {
|
|||||||
actualizarEstadoAutorizacionesMasivo(
|
actualizarEstadoAutorizacionesMasivo(
|
||||||
fechaInicio: string,
|
fechaInicio: string,
|
||||||
fechaFin: string,
|
fechaFin: string,
|
||||||
|
horaInicio: string | undefined,
|
||||||
|
horaFin: string | undefined,
|
||||||
estado: 'pendiente' | 'autorizado' | 'no_autorizado',
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado',
|
||||||
soloPendientes = false
|
soloPendientes = false
|
||||||
): Observable<any> {
|
): Observable<any> {
|
||||||
@ -415,6 +444,8 @@ export class PacienteService {
|
|||||||
{
|
{
|
||||||
fecha_inicio: fechaInicio,
|
fecha_inicio: fechaInicio,
|
||||||
fecha_fin: fechaFin,
|
fecha_fin: fechaFin,
|
||||||
|
hora_inicio: horaInicio,
|
||||||
|
hora_fin: horaFin,
|
||||||
estado_autorizacion: estado,
|
estado_autorizacion: estado,
|
||||||
solo_pendientes: soloPendientes,
|
solo_pendientes: soloPendientes,
|
||||||
},
|
},
|
||||||
@ -425,6 +456,8 @@ export class PacienteService {
|
|||||||
actualizarAutorizanteMasivo(
|
actualizarAutorizanteMasivo(
|
||||||
fechaInicio: string,
|
fechaInicio: string,
|
||||||
fechaFin: string,
|
fechaFin: string,
|
||||||
|
horaInicio: string | undefined,
|
||||||
|
horaFin: string | undefined,
|
||||||
numeroDocumentoAutorizante: number,
|
numeroDocumentoAutorizante: number,
|
||||||
establecimiento?: string,
|
establecimiento?: string,
|
||||||
ambito?: string,
|
ambito?: string,
|
||||||
@ -433,6 +466,8 @@ export class PacienteService {
|
|||||||
const payload: any = {
|
const payload: any = {
|
||||||
fecha_inicio: fechaInicio,
|
fecha_inicio: fechaInicio,
|
||||||
fecha_fin: fechaFin,
|
fecha_fin: fechaFin,
|
||||||
|
hora_inicio: horaInicio,
|
||||||
|
hora_fin: horaFin,
|
||||||
numero_documento_autorizante: numeroDocumentoAutorizante,
|
numero_documento_autorizante: numeroDocumentoAutorizante,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user