cambios nuevos
This commit is contained in:
parent
6ace09ec6c
commit
50e6aef612
@ -343,5 +343,3 @@ Reinicia el backend:
|
||||
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
|
||||
|
||||
|
||||
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):
|
||||
if not line:
|
||||
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:
|
||||
code = match.group(1).upper()
|
||||
desc = line[: match.start()].strip(" -:")
|
||||
desc = re.sub(r"(?i)diagnostico principal", "", desc).strip()
|
||||
desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip()
|
||||
code = normalize_cie10_code(match.group(1))
|
||||
desc = clean_diagnosis_desc(line[: match.start()].strip(" -:"))
|
||||
return code, desc.title() if desc else None
|
||||
match = re.search(r"\b([A-Z])(?:\s*\d){2,4}[A-Z0-9]?\b", line, re.IGNORECASE)
|
||||
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:
|
||||
code = re.sub(r"\s+", "", match.group(0)).upper()
|
||||
code = normalize_cie10_code(match.group(1))
|
||||
desc = line[match.end():].strip(" -:")
|
||||
if not desc:
|
||||
desc = line[: match.start()].strip(" -:")
|
||||
desc = re.sub(r"(?i)diagnostico principal", "", desc).strip()
|
||||
desc = re.sub(r"(?i)impresion diagnostica", "", desc).strip()
|
||||
return code, desc.title() if desc else None
|
||||
match = re.search(r"\b([A-Z][0-9]{2,4}[A-Z0-9]?)\b", line, re.IGNORECASE)
|
||||
if match:
|
||||
code = match.group(1).upper()
|
||||
desc = line[match.end():].strip(" -:")
|
||||
desc = clean_diagnosis_desc(desc)
|
||||
return code, desc.title() if desc else None
|
||||
return None, None
|
||||
|
||||
@ -390,20 +427,31 @@ def extract_cups_hint(lines, norm_lines):
|
||||
def extract_cie10_list(lines, norm_lines):
|
||||
results = []
|
||||
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):
|
||||
if (
|
||||
"DIAGNOSTICO" in nline
|
||||
or "IMPRESION DIAGNOSTICA" in nline
|
||||
or "CIE10" in nline
|
||||
):
|
||||
for j in range(i, min(i + 6, len(lines))):
|
||||
code, desc = parse_cie10_from_line(lines[j])
|
||||
if not code:
|
||||
continue
|
||||
if code in seen:
|
||||
continue
|
||||
if not any(token in nline for token in diag_tokens):
|
||||
continue
|
||||
for j in range(i, min(i + 12, len(lines))):
|
||||
code, desc = parse_cie10_from_line(lines[j])
|
||||
if code and code not in seen:
|
||||
seen.add(code)
|
||||
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
|
||||
|
||||
|
||||
@ -443,6 +491,37 @@ def extract_fecha_ingreso_urgencias(lines, norm_lines):
|
||||
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):
|
||||
if not value:
|
||||
return ""
|
||||
@ -549,6 +628,8 @@ def merge_response(base, extra):
|
||||
"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"):
|
||||
result["cups_busqueda"] = extra.get("cups_busqueda")
|
||||
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
|
||||
ips_nombre, ips_nit = extract_ips(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)
|
||||
|
||||
warnings = []
|
||||
@ -627,6 +709,7 @@ def build_response(text, ocr_used, ocr_available, ocr_error):
|
||||
"ips_nombre": ips_nombre,
|
||||
"ips_nit": ips_nit,
|
||||
"fecha_ingreso_urgencias": fecha_ingreso,
|
||||
"fecha_egreso": fecha_egreso,
|
||||
"warnings": warnings,
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,48 @@
|
||||
const ExcelJS = require('exceljs');
|
||||
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) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const sheet = workbook.addWorksheet('GAR-F-01');
|
||||
@ -2693,9 +2735,8 @@ async function crearLibroAutorizacion(a) {
|
||||
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
||||
});
|
||||
|
||||
// 2) Hora -> si no tienes hora en BD, usamos la hora actual como texto
|
||||
const ahora = new Date();
|
||||
const horaStr = ahora.toTimeString().slice(0, 5); // "HH:MM"
|
||||
// 2) Hora -> usamos la hora registrada en Bogota (o la actual si no hay fecha)
|
||||
const horaStr = formatBogotaTime(a.fecha_autorizacion) || formatBogotaTime(new Date());
|
||||
['K8', 'L8', 'M8'].forEach(celda => {
|
||||
sheet.getCell(celda).value = horaStr;
|
||||
});
|
||||
@ -2707,11 +2748,11 @@ async function crearLibroAutorizacion(a) {
|
||||
sheet.getCell(celda).value = versionValor;
|
||||
});
|
||||
|
||||
if (a.fecha_autorizacion) {
|
||||
const fecha = new Date(a.fecha_autorizacion);
|
||||
const fechaBogota = buildBogotaDate(a.fecha_autorizacion);
|
||||
if (fechaBogota) {
|
||||
['P8', 'Q8', 'R8'].forEach(celda => {
|
||||
const cell = sheet.getCell(celda);
|
||||
cell.value = fecha;
|
||||
cell.value = fechaBogota;
|
||||
cell.numFmt = 'dd/mm/yyyy';
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,6 +1,48 @@
|
||||
const ExcelJS = require('exceljs');
|
||||
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) {
|
||||
const workbook = new ExcelJS.Workbook();
|
||||
const sheet = workbook.addWorksheet("Hoja1");
|
||||
@ -2368,8 +2410,7 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
||||
sheet.getCell(celda).value = a.numero_autorizacion || '';
|
||||
});
|
||||
|
||||
const ahora = new Date();
|
||||
const horaStr = ahora.toTimeString().slice(0, 5);
|
||||
const horaStr = formatBogotaTime(a.fecha_autorizacion) || formatBogotaTime(new Date());
|
||||
['K8', 'L8', 'M8'].forEach((celda) => {
|
||||
sheet.getCell(celda).value = horaStr;
|
||||
});
|
||||
@ -2380,11 +2421,11 @@ async function crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a) {
|
||||
sheet.getCell(celda).value = versionValor;
|
||||
});
|
||||
|
||||
if (a.fecha_autorizacion) {
|
||||
const fecha = new Date(a.fecha_autorizacion);
|
||||
const fechaBogota = buildBogotaDate(a.fecha_autorizacion);
|
||||
if (fechaBogota) {
|
||||
['P8', 'Q8', 'R8'].forEach((celda) => {
|
||||
const cell = sheet.getCell(celda);
|
||||
cell.value = fecha;
|
||||
cell.value = fechaBogota;
|
||||
cell.numFmt = 'dd/mm/yyyy';
|
||||
});
|
||||
}
|
||||
|
||||
@ -53,6 +53,13 @@ CREATE TABLE IF NOT EXISTS ips (
|
||||
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 =====
|
||||
CREATE TABLE IF NOT EXISTS ingreso (
|
||||
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),
|
||||
numero_documento_autorizante bigint NOT NULL REFERENCES autorizante(numero_documento),
|
||||
id_usuario_solicitante integer REFERENCES usuario(id_usuario),
|
||||
fecha_autorizacion date NOT NULL DEFAULT current_date,
|
||||
fecha_autorizacion TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
observacion text,
|
||||
cup_codigo varchar(20),
|
||||
cie10_codigo text,
|
||||
@ -252,6 +259,11 @@ ALTER TABLE autorizacion
|
||||
ALTER COLUMN correo_inpec_enviado 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 COLUMN tipo_autorizacion 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,
|
||||
numero_documento_autorizante BIGINT NOT NULL,
|
||||
id_usuario_solicitante INTEGER,
|
||||
fecha_autorizacion DATE,
|
||||
fecha_autorizacion TIMESTAMPTZ,
|
||||
observacion TEXT,
|
||||
cup_codigo VARCHAR(20),
|
||||
cie10_codigo TEXT,
|
||||
@ -341,6 +353,10 @@ CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||
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 =====
|
||||
CREATE TABLE IF NOT EXISTS profesional_reps (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
@ -111,6 +111,129 @@ const normalizeSearch = (value) =>
|
||||
.toUpperCase()
|
||||
.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) => {
|
||||
if (!file) return false;
|
||||
const mime = String(file.mimetype || '').toLowerCase();
|
||||
@ -350,18 +473,33 @@ 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 () => {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||
id SERIAL PRIMARY KEY,
|
||||
numero_autorizacion VARCHAR(50) NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
id_ips INTEGER NOT NULL,
|
||||
numero_documento_autorizante BIGINT NOT NULL,
|
||||
id_usuario_solicitante INTEGER,
|
||||
fecha_autorizacion DATE,
|
||||
observacion TEXT,
|
||||
cup_codigo VARCHAR(20),
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||
id SERIAL PRIMARY KEY,
|
||||
numero_autorizacion VARCHAR(50) NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
id_ips INTEGER NOT NULL,
|
||||
numero_documento_autorizante BIGINT NOT NULL,
|
||||
id_usuario_solicitante INTEGER,
|
||||
fecha_autorizacion TIMESTAMPTZ,
|
||||
observacion TEXT,
|
||||
cup_codigo VARCHAR(20),
|
||||
cie10_codigo VARCHAR(20),
|
||||
cie10_descripcion TEXT,
|
||||
tipo_autorizacion VARCHAR(50),
|
||||
@ -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(`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS autorizacion_version_unique
|
||||
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 () => {
|
||||
await pool.query(`
|
||||
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(
|
||||
'/api/cargar-ips',
|
||||
verificarToken,
|
||||
@ -1507,6 +1734,24 @@ const normalizeDigits = (value) => String(value || '').replace(/\D/g, '');
|
||||
const normalizeNameKey = (value) =>
|
||||
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([
|
||||
'NA',
|
||||
'SD',
|
||||
@ -1529,6 +1774,108 @@ const cleanIpsField = (value) => {
|
||||
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 text = String(value || '').trim();
|
||||
if (!text) return '';
|
||||
@ -1730,54 +2077,65 @@ async function procesarExcelIps(inputFilePath) {
|
||||
|
||||
const nombreKey = normalizeNameKey(nombre);
|
||||
if (nombreKey) nombreKeys.add(nombreKey);
|
||||
const departamentoKey = normalizeLocationKey(departamento);
|
||||
const municipioKey = normalizeLocationKey(municipio);
|
||||
|
||||
let existente = null;
|
||||
if (lookupKeys.length > 0) {
|
||||
const res = await client.query(
|
||||
`
|
||||
SELECT id_ips
|
||||
SELECT id_ips, departamento, municipio
|
||||
FROM ips
|
||||
WHERE regexp_replace(nit, '\\D', '', 'g') = ANY($1::text[])
|
||||
OR regexp_replace(codigo_ips, '\\D', '', 'g') = ANY($1::text[])
|
||||
LIMIT 1
|
||||
`,
|
||||
[lookupKeys]
|
||||
);
|
||||
existente = res.rows[0] || null;
|
||||
existente = pickIpsRowByLocation(
|
||||
res.rows,
|
||||
departamentoKey,
|
||||
municipioKey
|
||||
);
|
||||
}
|
||||
|
||||
if (!existente && nitDigits && nitDigits.length >= 8) {
|
||||
const res = await client.query(
|
||||
`
|
||||
SELECT id_ips
|
||||
SELECT id_ips, departamento, municipio
|
||||
FROM ips
|
||||
WHERE regexp_replace(nit, '\\D', '', 'g') LIKE $1
|
||||
AND length(regexp_replace(nit, '\\D', '', 'g')) = $2
|
||||
LIMIT 1
|
||||
`,
|
||||
[`${nitDigits}%`, nitDigits.length + 1]
|
||||
);
|
||||
existente = res.rows[0] || null;
|
||||
existente = pickIpsRowByLocation(
|
||||
res.rows,
|
||||
departamentoKey,
|
||||
municipioKey
|
||||
);
|
||||
}
|
||||
|
||||
if (!existente && codigoDigits && codigoDigits.length >= 8) {
|
||||
const res = await client.query(
|
||||
`
|
||||
SELECT id_ips
|
||||
SELECT id_ips, departamento, municipio
|
||||
FROM ips
|
||||
WHERE regexp_replace(codigo_ips, '\\D', '', 'g') LIKE $1
|
||||
AND length(regexp_replace(codigo_ips, '\\D', '', 'g')) = $2
|
||||
LIMIT 1
|
||||
`,
|
||||
[`${codigoDigits}%`, codigoDigits.length + 1]
|
||||
);
|
||||
existente = res.rows[0] || null;
|
||||
existente = pickIpsRowByLocation(
|
||||
res.rows,
|
||||
departamentoKey,
|
||||
municipioKey
|
||||
);
|
||||
}
|
||||
|
||||
if (!existente && nombreKey) {
|
||||
const res = await client.query(
|
||||
`
|
||||
SELECT id_ips
|
||||
SELECT id_ips, departamento, municipio
|
||||
FROM ips
|
||||
WHERE regexp_replace(
|
||||
translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
|
||||
@ -1785,11 +2143,14 @@ async function procesarExcelIps(inputFilePath) {
|
||||
'',
|
||||
'g'
|
||||
) = $1
|
||||
LIMIT 1
|
||||
`,
|
||||
[nombreKey]
|
||||
);
|
||||
existente = res.rows[0] || null;
|
||||
existente = pickIpsRowByLocation(
|
||||
res.rows,
|
||||
departamentoKey,
|
||||
municipioKey
|
||||
);
|
||||
}
|
||||
|
||||
if (existente) {
|
||||
@ -2601,7 +2962,7 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) {
|
||||
`
|
||||
INSERT INTO autorizacion
|
||||
(interno, id_ips, numero_documento_autorizante, id_usuario_solicitante, fecha_autorizacion, observacion, cup_codigo, cie10_codigo, cie10_descripcion, tipo_autorizacion, tipo_servicio, ambito_atencion, numero_orden, estado_entrega, estado_autorizacion)
|
||||
VALUES ($1, $2, $3, $4, COALESCE($5::date, current_date), $6, $7, $8, $9, $10, $11, $12, $13, $14, 'pendiente')
|
||||
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;
|
||||
`,
|
||||
[
|
||||
@ -2879,7 +3240,9 @@ async function generarZipAutorizacionesPorFecha(
|
||||
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 joinParts = [];
|
||||
if (soloAutorizadas) {
|
||||
@ -2930,9 +3293,15 @@ async function generarZipAutorizacionesPorFecha(
|
||||
const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
|
||||
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(
|
||||
jobTmpDir,
|
||||
`autorizaciones_${fechaInicio}_${fechaFin}.zip`
|
||||
`autorizaciones_${safeInicio}_${safeFin}.zip`
|
||||
);
|
||||
|
||||
const output = fs.createWriteStream(zipPath);
|
||||
@ -3126,18 +3495,35 @@ const crearJobPdfHandler = async (req, res) => {
|
||||
return res.status(202).json(sanitizeJob(job));
|
||||
};
|
||||
|
||||
const crearJobZipHandler = async (req, res) => {
|
||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||
const establecimiento = req.body?.establecimiento || req.query?.establecimiento;
|
||||
const ambito = req.body?.ambito || req.query?.ambito;
|
||||
const ips = req.body?.ips || req.query?.ips;
|
||||
const esAdmin = req.usuario?.nombre_rol === 'administrador';
|
||||
const soloAutorizadas = !esAdmin;
|
||||
const crearJobZipHandler = async (req, res) => {
|
||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||
const 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 ambito = req.body?.ambito || req.query?.ambito;
|
||||
const ips = req.body?.ips || req.query?.ips;
|
||||
const esAdmin = req.usuario?.nombre_rol === 'administrador';
|
||||
const soloAutorizadas = !esAdmin;
|
||||
|
||||
if (!fecha_inicio || !fecha_fin) {
|
||||
return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
|
||||
}
|
||||
if (!fecha_inicio || !fecha_fin) {
|
||||
return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
|
||||
}
|
||||
|
||||
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 job = {
|
||||
@ -3150,18 +3536,20 @@ const crearJobPdfHandler = async (req, res) => {
|
||||
|
||||
jobs.set(jobId, job);
|
||||
|
||||
enqueueJob(job, async () => {
|
||||
const zipPath = await generarZipAutorizacionesPorFecha(fecha_inicio, fecha_fin, jobId, {
|
||||
establecimiento,
|
||||
ambito,
|
||||
ips,
|
||||
soloAutorizadas,
|
||||
});
|
||||
return {
|
||||
ok: true,
|
||||
mensaje: 'ZIP generado correctamente',
|
||||
enqueueJob(job, async () => {
|
||||
const zipPath = await generarZipAutorizacionesPorFecha(rango.inicio, rango.fin, jobId, {
|
||||
establecimiento,
|
||||
ambito,
|
||||
ips,
|
||||
soloAutorizadas,
|
||||
});
|
||||
const safeInicio = String(rango.inicio).replace(/[:]/g, '').replace(/\s+/g, '_');
|
||||
const safeFin = String(rango.fin).replace(/[:]/g, '').replace(/\s+/g, '_');
|
||||
return {
|
||||
ok: true,
|
||||
mensaje: 'ZIP generado correctamente',
|
||||
filePath: zipPath,
|
||||
fileName: `autorizaciones_${fecha_inicio}_${fecha_fin}.zip`,
|
||||
fileName: `autorizaciones_${safeInicio}_${safeFin}.zip`,
|
||||
contentType: 'application/zip',
|
||||
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) => {
|
||||
const { numero_documento, interno, nombre } = req.query;
|
||||
|
||||
@ -3632,6 +4062,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
||||
numero_documento_autorizante,
|
||||
observacion,
|
||||
fecha_autorizacion,
|
||||
hora_autorizacion,
|
||||
cup_codigo,
|
||||
cie10_codigo,
|
||||
cie10_descripcion,
|
||||
@ -3653,6 +4084,19 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
||||
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 cie10Descripcion = String(cie10_descripcion || '').trim();
|
||||
if (!cie10Codigo) {
|
||||
@ -3745,7 +4189,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
||||
numero_orden,
|
||||
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;
|
||||
`;
|
||||
|
||||
@ -3754,7 +4198,7 @@ app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, asyn
|
||||
id_ips,
|
||||
numero_documento_autorizante,
|
||||
solicitanteId,
|
||||
fecha_autorizacion || null,
|
||||
fechaAutorizacionInput,
|
||||
observacion || null,
|
||||
cupCodigo,
|
||||
cie10Codigo,
|
||||
@ -3884,6 +4328,7 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
||||
numero_documento_autorizante,
|
||||
observacion,
|
||||
fecha_autorizacion,
|
||||
hora_autorizacion,
|
||||
cup_codigo,
|
||||
cie10_codigo,
|
||||
cie10_descripcion,
|
||||
@ -3905,6 +4350,19 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
||||
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 cie10DescripcionInput = String(cie10_descripcion || '').trim();
|
||||
const ambitoInput = String(ambito_atencion || '').trim().toLowerCase();
|
||||
@ -4034,14 +4492,14 @@ app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerar
|
||||
}
|
||||
|
||||
const nuevaVersion = versionActual + 1;
|
||||
const fechaAut = fecha_autorizacion || actual.fecha_autorizacion || null;
|
||||
const fechaAut = fechaAutorizacionInput || null;
|
||||
|
||||
const updateRes = await client.query(
|
||||
`
|
||||
UPDATE autorizacion
|
||||
SET id_ips = $1,
|
||||
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,
|
||||
cup_codigo = $5,
|
||||
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) => {
|
||||
const { id } = req.params;
|
||||
const { username, password, email, nombre_completo, id_rol } = req.body || {};
|
||||
let idRolActualizado = null;
|
||||
|
||||
const idUsuario = Number(id);
|
||||
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) => {
|
||||
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 ambito = String(req.query.ambito || '').trim().toLowerCase();
|
||||
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' });
|
||||
}
|
||||
|
||||
const whereParts = ['a.fecha_autorizacion BETWEEN $1 AND $2'];
|
||||
const params = [fecha_inicio, fecha_fin];
|
||||
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 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) {
|
||||
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(*) AS total
|
||||
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 = `
|
||||
@ -5032,7 +5511,7 @@ app.get('/api/autorizaciones-estadisticas', verificarToken, esAdministrador, asy
|
||||
COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes
|
||||
FROM dias d
|
||||
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
|
||||
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) => {
|
||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||
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 || '')
|
||||
.trim()
|
||||
.toLowerCase();
|
||||
@ -5078,12 +5559,28 @@ app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador,
|
||||
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 {
|
||||
const params = [estado, fecha_inicio, fecha_fin];
|
||||
const params = [estado, rango.inicio, rango.fin];
|
||||
let sql = `
|
||||
UPDATE autorizacion
|
||||
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) {
|
||||
@ -5111,6 +5608,8 @@ app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador,
|
||||
app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministrador, async (req, res) => {
|
||||
const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
|
||||
const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
|
||||
const hora_inicio = req.body?.hora_inicio || req.query?.hora_inicio;
|
||||
const hora_fin = req.body?.hora_fin || req.query?.hora_fin;
|
||||
const numeroDocumento =
|
||||
req.body?.numero_documento_autorizante || req.query?.numero_documento_autorizante;
|
||||
const establecimiento = String(
|
||||
@ -5135,6 +5634,21 @@ app.patch('/api/autorizaciones/autorizante-masivo', verificarToken, esAdministra
|
||||
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 {
|
||||
const autorizanteRes = await pool.query(
|
||||
'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' });
|
||||
}
|
||||
|
||||
const whereParts = ['a.fecha_autorizacion BETWEEN $2::date AND $3::date'];
|
||||
const params = [numeroAutorizante, fecha_inicio, fecha_fin];
|
||||
const whereParts = [
|
||||
`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) {
|
||||
params.push(`%${establecimiento}%`);
|
||||
@ -5207,6 +5723,10 @@ ensureCupsTables().catch((error) => {
|
||||
console.error('Error inicializando tablas CUPS:', error.message);
|
||||
});
|
||||
|
||||
ensureCie10Table().catch((error) => {
|
||||
console.error('Error inicializando tabla CIE-10:', error.message);
|
||||
});
|
||||
|
||||
ensureAutorizacionVersionTables().catch((error) => {
|
||||
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);
|
||||
});
|
||||
|
||||
ensureAutorizacionFechaHora().catch((error) => {
|
||||
console.error('Error inicializando fecha/hora de autorizaciones:', error.message);
|
||||
});
|
||||
|
||||
ensureIpsConvenio().catch((error) => {
|
||||
console.error('Error inicializando convenio de IPS:', error.message);
|
||||
});
|
||||
|
||||
@ -60,6 +60,26 @@
|
||||
</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">
|
||||
<button
|
||||
type="submit"
|
||||
|
||||
@ -47,6 +47,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
// Fechas en formato yyyy-MM-dd para usar en la API
|
||||
private fechaInicioApi: string | null = null;
|
||||
private fechaFinApi: string | null = null;
|
||||
private horaInicioApi: string | null = null;
|
||||
private horaFinApi: string | null = null;
|
||||
|
||||
constructor(
|
||||
private fb: FormBuilder,
|
||||
@ -59,6 +61,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
this.filtroForm = this.fb.group({
|
||||
fecha_inicio: ['', Validators.required],
|
||||
fecha_fin: ['', Validators.required],
|
||||
hora_inicio: [''],
|
||||
hora_fin: [''],
|
||||
establecimiento: [''],
|
||||
ambito: [''],
|
||||
ips: [''],
|
||||
@ -81,7 +85,9 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
|
||||
this.filtroForm.patchValue({
|
||||
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;
|
||||
}
|
||||
|
||||
const inicio = new Date(this.fecha_inicio.value);
|
||||
const fin = new Date(this.fecha_fin.value);
|
||||
const fechaInicio = this.normalizeDateInput(this.fecha_inicio.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.';
|
||||
return;
|
||||
}
|
||||
|
||||
// Guardamos las fechas ya formateadas para reutilizarlas
|
||||
this.fechaInicioApi = this.formatDateForInput(inicio);
|
||||
this.fechaFinApi = this.formatDateForInput(fin);
|
||||
this.fechaInicioApi = fechaInicio;
|
||||
this.fechaFinApi = fechaFin;
|
||||
this.horaInicioApi = horaInicio;
|
||||
this.horaFinApi = horaFin;
|
||||
this.establecimientoFiltro = String(this.filtroForm.get('establecimiento')?.value || '').trim();
|
||||
this.ambitoFiltro = String(this.filtroForm.get('ambito')?.value || '').trim().toLowerCase();
|
||||
this.ipsFiltro = String(this.filtroForm.get('ips')?.value || '').trim();
|
||||
@ -118,6 +146,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
.getAutorizacionesPorFecha(
|
||||
this.fechaInicioApi!,
|
||||
this.fechaFinApi!,
|
||||
this.horaInicioApi || undefined,
|
||||
this.horaFinApi || undefined,
|
||||
this.limiteAutorizaciones,
|
||||
0,
|
||||
this.establecimientoFiltro || undefined,
|
||||
@ -286,6 +316,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
.actualizarEstadoAutorizacionesMasivo(
|
||||
this.fechaInicioApi,
|
||||
this.fechaFinApi,
|
||||
this.horaInicioApi || undefined,
|
||||
this.horaFinApi || undefined,
|
||||
this.estadoMasivo
|
||||
)
|
||||
.subscribe({
|
||||
@ -348,6 +380,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
.actualizarAutorizanteMasivo(
|
||||
this.fechaInicioApi,
|
||||
this.fechaFinApi,
|
||||
this.horaInicioApi || undefined,
|
||||
this.horaFinApi || undefined,
|
||||
autorizadoId,
|
||||
this.establecimientoFiltro || undefined,
|
||||
this.ambitoFiltro || undefined,
|
||||
@ -466,6 +500,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
.crearJobZipAutorizaciones(
|
||||
this.fechaInicioApi,
|
||||
this.fechaFinApi,
|
||||
this.horaInicioApi || undefined,
|
||||
this.horaFinApi || undefined,
|
||||
this.establecimientoFiltro || undefined,
|
||||
this.ambitoFiltro || undefined,
|
||||
this.ipsFiltro || undefined
|
||||
@ -475,9 +511,15 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
this.jobsService.pollJob(job.id).subscribe({
|
||||
next: (estado) => {
|
||||
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 =
|
||||
estado.result?.fileName ||
|
||||
`autorizaciones_${this.fechaInicioApi}_${this.fechaFinApi}.zip`;
|
||||
`autorizaciones_${inicioLabel}_${finLabel}.zip`;
|
||||
|
||||
this.jobsService.downloadJobFile(job.id).subscribe({
|
||||
next: (blob) => {
|
||||
@ -536,8 +578,59 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
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 {
|
||||
const date = new Date(dateString);
|
||||
const date = this.parseLocalDateTime(dateString);
|
||||
if (!date) return '';
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
@ -546,7 +639,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
}
|
||||
|
||||
formatDateTime(dateString: string): string {
|
||||
const date = new Date(dateString);
|
||||
const date = this.parseLocalDateTime(dateString);
|
||||
if (!date) return '';
|
||||
return date.toLocaleDateString('es-ES', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
@ -709,14 +803,33 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
}
|
||||
|
||||
validarFechas(): void {
|
||||
const fechaInicio = this.filtroForm.get('fecha_inicio')?.value;
|
||||
const fechaFin = this.filtroForm.get('fecha_fin')?.value;
|
||||
const fechaInicioRaw = this.filtroForm.get('fecha_inicio')?.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 });
|
||||
} else {
|
||||
this.filtroForm.get('fecha_fin')?.setErrors(null);
|
||||
return;
|
||||
}
|
||||
|
||||
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 =========
|
||||
|
||||
@ -72,6 +72,17 @@
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cie10-row .cie10-input {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cie10-row .cie10-input input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cup-results {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
@ -377,14 +377,30 @@
|
||||
{{ errorCups }}
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-row cie10-row">
|
||||
<label for="cie10Codigo">Codigo CIE-10:</label>
|
||||
<input
|
||||
id="cie10Codigo"
|
||||
type="text"
|
||||
[(ngModel)]="formAutorizacion.cie10_codigo"
|
||||
placeholder="Ej: A00"
|
||||
/>
|
||||
<div class="cie10-input">
|
||||
<input
|
||||
id="cie10Codigo"
|
||||
type="text"
|
||||
[(ngModel)]="formAutorizacion.cie10_codigo"
|
||||
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 class="form-row">
|
||||
@ -404,6 +420,15 @@
|
||||
[(ngModel)]="formAutorizacion.fecha_autorizacion"
|
||||
/>
|
||||
</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">
|
||||
<label for="obs">Observación / servicios autorizados:</label>
|
||||
|
||||
@ -35,6 +35,9 @@ export class AutorizacionesComponent {
|
||||
cupSeleccionado: CupInfo | null = null;
|
||||
buscandoCups = false;
|
||||
errorCups: string | null = null;
|
||||
buscandoCie10 = false;
|
||||
errorCie10: string | null = null;
|
||||
private cie10Timer: ReturnType<typeof setTimeout> | null = null;
|
||||
verMasIps = false;
|
||||
departamentoInterno = '';
|
||||
observacionTraslado = '';
|
||||
@ -50,6 +53,7 @@ export class AutorizacionesComponent {
|
||||
id_ips: '',
|
||||
numero_documento_autorizante: '',
|
||||
fecha_autorizacion: '',
|
||||
hora_autorizacion: '',
|
||||
observacion: '',
|
||||
cup_codigo: '',
|
||||
cie10_codigo: '',
|
||||
@ -146,6 +150,7 @@ export class AutorizacionesComponent {
|
||||
id_ips: '',
|
||||
numero_documento_autorizante: '',
|
||||
fecha_autorizacion: '',
|
||||
hora_autorizacion: '',
|
||||
observacion: '',
|
||||
cup_codigo: '',
|
||||
cie10_codigo: '',
|
||||
@ -155,6 +160,7 @@ export class AutorizacionesComponent {
|
||||
ambito_atencion: '',
|
||||
numero_orden: '',
|
||||
};
|
||||
this.setFechaHoraActual();
|
||||
|
||||
this.autorizacionesPaciente = [];
|
||||
this.versionesPorAutorizacion = {};
|
||||
@ -165,6 +171,11 @@ export class AutorizacionesComponent {
|
||||
this.cupsDisponibles = [];
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCups = null;
|
||||
this.errorCie10 = null;
|
||||
if (this.cie10Timer) {
|
||||
clearTimeout(this.cie10Timer);
|
||||
this.cie10Timer = null;
|
||||
}
|
||||
|
||||
this.cargarIps(p.interno, this.verMasIps);
|
||||
this.cargarAutorizantes();
|
||||
@ -180,6 +191,11 @@ export class AutorizacionesComponent {
|
||||
this.cupsDisponibles = [];
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCups = null;
|
||||
this.errorCie10 = null;
|
||||
if (this.cie10Timer) {
|
||||
clearTimeout(this.cie10Timer);
|
||||
this.cie10Timer = null;
|
||||
}
|
||||
this.verMasIps = false;
|
||||
this.departamentoInterno = '';
|
||||
this.observacionTraslado = '';
|
||||
@ -332,6 +348,63 @@ export class AutorizacionesComponent {
|
||||
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
|
||||
// -------------------------
|
||||
@ -459,6 +532,9 @@ export class AutorizacionesComponent {
|
||||
if (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) {
|
||||
const actual = String(this.formAutorizacion.observacion || '').trim();
|
||||
const nuevos = observacionExtras.filter(
|
||||
@ -705,7 +781,42 @@ export class AutorizacionesComponent {
|
||||
|
||||
private formatDateInput(value: string | null | undefined): string {
|
||||
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 {
|
||||
@ -714,6 +825,7 @@ export class AutorizacionesComponent {
|
||||
this.autorizacionEditando = autorizacion;
|
||||
this.mensajeAutorizacion = null;
|
||||
this.errorAutorizacion = null;
|
||||
this.errorCie10 = null;
|
||||
|
||||
this.formAutorizacion = {
|
||||
id_ips: String(autorizacion.id_ips || ''),
|
||||
@ -721,6 +833,7 @@ export class AutorizacionesComponent {
|
||||
autorizacion.numero_documento_autorizante || ''
|
||||
),
|
||||
fecha_autorizacion: this.formatDateInput(autorizacion.fecha_autorizacion),
|
||||
hora_autorizacion: this.formatTimeInput(autorizacion.fecha_autorizacion),
|
||||
observacion: autorizacion.observacion || '',
|
||||
cup_codigo: autorizacion.cup_codigo || '',
|
||||
cie10_codigo: autorizacion.cie10_codigo || '',
|
||||
@ -745,10 +858,16 @@ export class AutorizacionesComponent {
|
||||
this.autorizacionEditando = null;
|
||||
this.observacionTraslado = '';
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCie10 = null;
|
||||
if (this.cie10Timer) {
|
||||
clearTimeout(this.cie10Timer);
|
||||
this.cie10Timer = null;
|
||||
}
|
||||
this.formAutorizacion = {
|
||||
id_ips: '',
|
||||
numero_documento_autorizante: '',
|
||||
fecha_autorizacion: '',
|
||||
hora_autorizacion: '',
|
||||
observacion: '',
|
||||
cup_codigo: '',
|
||||
cie10_codigo: '',
|
||||
@ -758,6 +877,7 @@ export class AutorizacionesComponent {
|
||||
ambito_atencion: '',
|
||||
numero_orden: '',
|
||||
};
|
||||
this.setFechaHoraActual();
|
||||
this.limpiarArchivosHospitalarios();
|
||||
}
|
||||
|
||||
@ -785,6 +905,14 @@ export class AutorizacionesComponent {
|
||||
this.errorAutorizacion = 'Debe seleccionar quién autoriza.';
|
||||
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) {
|
||||
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
||||
return;
|
||||
@ -847,6 +975,8 @@ export class AutorizacionesComponent {
|
||||
observacion: this.buildObservacionFinal(),
|
||||
fecha_autorizacion:
|
||||
this.formAutorizacion.fecha_autorizacion || undefined,
|
||||
hora_autorizacion:
|
||||
this.formAutorizacion.hora_autorizacion || undefined,
|
||||
cup_codigo: this.formAutorizacion.cup_codigo,
|
||||
cie10_codigo: this.formAutorizacion.cie10_codigo,
|
||||
cie10_descripcion: this.formAutorizacion.cie10_descripcion,
|
||||
|
||||
@ -45,5 +45,31 @@
|
||||
<div class="status ok" *ngIf="statusMessage">{{ statusMessage }}</div>
|
||||
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</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>
|
||||
|
||||
@ -16,9 +16,13 @@ import { JobsService } from '../../services/jobs';
|
||||
export class CargarCupsComponent implements OnInit {
|
||||
notaFile: File | null = null;
|
||||
referenciaFile: File | null = null;
|
||||
cie10File: File | null = null;
|
||||
isLoading = false;
|
||||
cie10Loading = false;
|
||||
statusMessage: string | null = null;
|
||||
errorMessage: string | null = null;
|
||||
cie10StatusMessage: string | null = null;
|
||||
cie10ErrorMessage: string | null = null;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
@ -62,6 +66,12 @@ export class CargarCupsComponent implements OnInit {
|
||||
this.limpiarMensajes();
|
||||
}
|
||||
|
||||
onCie10Selected(event: Event): void {
|
||||
const input = event.target as HTMLInputElement;
|
||||
this.cie10File = input.files?.[0] || null;
|
||||
this.limpiarMensajesCie10();
|
||||
}
|
||||
|
||||
procesarCups(): void {
|
||||
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 {
|
||||
this.errorMessage = 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.authService
|
||||
.getAutorizacionesPorFecha(bucket.inicio, bucket.fin, 500, 0)
|
||||
.getAutorizacionesPorFecha(bucket.inicio, bucket.fin, undefined, undefined, 500, 0)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.isLoadingDetalle = false;
|
||||
|
||||
@ -312,6 +312,8 @@ export class AuthService {
|
||||
getAutorizacionesPorFecha(
|
||||
fechaInicio: string,
|
||||
fechaFin: string,
|
||||
horaInicio?: string,
|
||||
horaFin?: string,
|
||||
limit = 100000,
|
||||
offset = 0,
|
||||
establecimiento?: string,
|
||||
@ -320,6 +322,12 @@ export class AuthService {
|
||||
): Observable<any[]> {
|
||||
const headers = this.getAuthHeaders();
|
||||
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) {
|
||||
params.establecimiento = establecimiento;
|
||||
}
|
||||
@ -371,12 +379,20 @@ export class AuthService {
|
||||
crearJobZipAutorizaciones(
|
||||
fechaInicio: string,
|
||||
fechaFin: string,
|
||||
horaInicio?: string,
|
||||
horaFin?: string,
|
||||
establecimiento?: string,
|
||||
ambito?: string,
|
||||
ips?: string
|
||||
): Observable<JobResponse> {
|
||||
const headers = this.getAuthHeaders();
|
||||
const payload: any = { fecha_inicio: fechaInicio, fecha_fin: fechaFin };
|
||||
if (horaInicio) {
|
||||
payload.hora_inicio = horaInicio;
|
||||
}
|
||||
if (horaFin) {
|
||||
payload.hora_fin = horaFin;
|
||||
}
|
||||
if (establecimiento) {
|
||||
payload.establecimiento = establecimiento;
|
||||
}
|
||||
|
||||
@ -21,6 +21,7 @@ export interface JobResult {
|
||||
actualizados?: number | null;
|
||||
omitidos?: number | null;
|
||||
omitidas?: number | null;
|
||||
procesadas?: number | null;
|
||||
desactivados?: number | null;
|
||||
duplicados?: number | null;
|
||||
creadas?: number | null;
|
||||
|
||||
@ -76,6 +76,7 @@ export interface CrearAutorizacionPayload {
|
||||
estado_entrega?: string;
|
||||
observacion?: string;
|
||||
fecha_autorizacion?: string; // yyyy-MM-dd
|
||||
hora_autorizacion?: string; // HH:mm
|
||||
}
|
||||
|
||||
export interface RespuestaAutorizacion {
|
||||
@ -136,6 +137,11 @@ export interface CupInfo {
|
||||
cubierto: boolean;
|
||||
}
|
||||
|
||||
export interface Cie10Info {
|
||||
codigo: string;
|
||||
descripcion: string;
|
||||
}
|
||||
|
||||
// ====== Servicio ======
|
||||
|
||||
@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> {
|
||||
const token = this.authService.getToken();
|
||||
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(
|
||||
numeroAutorizacion: string,
|
||||
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
||||
@ -407,6 +434,8 @@ export class PacienteService {
|
||||
actualizarEstadoAutorizacionesMasivo(
|
||||
fechaInicio: string,
|
||||
fechaFin: string,
|
||||
horaInicio: string | undefined,
|
||||
horaFin: string | undefined,
|
||||
estado: 'pendiente' | 'autorizado' | 'no_autorizado',
|
||||
soloPendientes = false
|
||||
): Observable<any> {
|
||||
@ -415,6 +444,8 @@ export class PacienteService {
|
||||
{
|
||||
fecha_inicio: fechaInicio,
|
||||
fecha_fin: fechaFin,
|
||||
hora_inicio: horaInicio,
|
||||
hora_fin: horaFin,
|
||||
estado_autorizacion: estado,
|
||||
solo_pendientes: soloPendientes,
|
||||
},
|
||||
@ -425,6 +456,8 @@ export class PacienteService {
|
||||
actualizarAutorizanteMasivo(
|
||||
fechaInicio: string,
|
||||
fechaFin: string,
|
||||
horaInicio: string | undefined,
|
||||
horaFin: string | undefined,
|
||||
numeroDocumentoAutorizante: number,
|
||||
establecimiento?: string,
|
||||
ambito?: string,
|
||||
@ -433,6 +466,8 @@ export class PacienteService {
|
||||
const payload: any = {
|
||||
fecha_inicio: fechaInicio,
|
||||
fecha_fin: fechaFin,
|
||||
hora_inicio: horaInicio,
|
||||
hora_fin: horaFin,
|
||||
numero_documento_autorizante: numeroDocumentoAutorizante,
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user