diff --git a/PLANTILLAAUT.xlsx b/PLANTILLAAUT.xlsx new file mode 100644 index 0000000..00356f4 Binary files /dev/null and b/PLANTILLAAUT.xlsx differ diff --git a/backend/src/server.js b/backend/src/server.js index 92a3588..86a87ab 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -1358,6 +1358,63 @@ app.post( } ); +app.post( + '/api/cargar-autorizados-masivos', + 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: 'autorizados-masivos', + 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, `autorizados_${jobId}.xlsx`); + await fsPromises.writeFile(inputPath, req.file.buffer); + + enqueueJob(job, async () => { + try { + return await procesarExcelAutorizacionesMasivas(inputPath, req.usuario, { + estadoAutorizacion: 'autorizado', + allowMissingAmbito: true, + defaultAmbito: 'extramural', + allowNumeroOrdenForAnyAmbito: true, + fechaAutorizacionKeys: [ + 'FECHADEREGISTRO', + 'FECHAREGISTRO', + 'FECHADEAUTORIZACION', + 'FECHA', + ], + }); + } finally { + await safeUnlink(inputPath); + await safeRemoveDir(jobTmpDir); + } + }); + + return res.status(202).json(sanitizeJob(job)); + } catch (error) { + console.error('Error creando job de autorizados masivos:', error); + return res.status(500).json({ + error: 'Error creando el job para procesar autorizados masivos', + }); + } + } +); + app.post( '/api/autorizaciones/autorrellenar', verificarToken, @@ -2444,7 +2501,7 @@ async function procesarExcelReps(inputFilePath) { }; } -async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { +async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario, options = {}) { const workbook = new ExcelJS.Workbook(); await workbook.xlsx.readFile(inputFilePath); const sheet = workbook.worksheets[0]; @@ -2453,6 +2510,14 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { throw new Error('No se encontro una hoja en el Excel de autorizaciones'); } + const { + estadoAutorizacion = 'pendiente', + allowMissingAmbito = false, + defaultAmbito = '', + allowNumeroOrdenForAnyAmbito = false, + fechaAutorizacionKeys = [], + } = options; + const headerRow = sheet.getRow(1); const headers = {}; headerRow.eachCell((cell, colNumber) => { @@ -2544,6 +2609,7 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { 'NUMERODEDOCUMENTOPPL', 'NUMERODOCUMENTOPPL', 'DOCUMENTOPPL', + 'NUMERODEDOCUMENTO', 'NUMERODOCUMENTO', ]); const cupsRaw = getValueMulti(row, [ @@ -2653,15 +2719,19 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { ambitoAtencion = 'extramural'; } if (!ambitoAtencion) { - resumen.omitidas += 1; - resumen.sin_ambito += 1; - if (resumen.errores.length < 50) { - resumen.errores.push({ - fila: i, - error: 'Ambito intramural/extramural invalido', - }); + if (allowMissingAmbito) { + ambitoAtencion = defaultAmbito ? defaultAmbito : null; + } else { + resumen.omitidas += 1; + resumen.sin_ambito += 1; + if (resumen.errores.length < 50) { + resumen.errores.push({ + fila: i, + error: 'Ambito intramural/extramural invalido', + }); + } + continue; } - continue; } const numeroOrdenRaw = getValueMulti(row, [ @@ -2670,10 +2740,15 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { 'NUMEROORDEN', 'NROORDEN', 'NUMERODELAORDEN', + 'CODIGPODEREGISTRO', + 'CODIGODEREGISTRO', + 'CODIGOREGISTRO', ]); const numeroOrden = String(numeroOrdenRaw || '').trim(); const numeroOrdenFinal = - ambitoAtencion === 'intramural' && numeroOrden ? numeroOrden : null; + (ambitoAtencion === 'intramural' || allowNumeroOrdenForAnyAmbito) && numeroOrden + ? numeroOrden + : null; const estadoEntregaRaw = getValueMulti(row, [ 'ESTADODEENTREGA', @@ -2684,6 +2759,13 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { const estadoEntrega = parseEstadoEntrega(estadoEntregaRaw) || 'pendiente_entrega'; + const fechaAutorizacionRaw = fechaAutorizacionKeys.length + ? getValueMulti(row, fechaAutorizacionKeys) + : ''; + const fechaAutorizacion = fechaAutorizacionRaw + ? String(fechaAutorizacionRaw).trim() + : null; + const cupCodigo = extractCupCodigo(cupsRaw || procedimiento); if (!cupCodigo) { resumen.omitidas += 1; @@ -2720,6 +2802,8 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { 'NOMBREIPSREMITIDO', 'NOMBREDEIPSREMITIDO', 'IPSREMITIDO', + 'IPSSUJERIDA', + 'IPSSUGERIDA', ]); const departamentoExcel = getValue(row, 'DEPARTAMENTO') || @@ -2962,7 +3046,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::timestamp AT TIME ZONE '${BOGOTA_TIMEZONE}', now()), $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, $15) RETURNING numero_autorizacion, fecha_autorizacion, version; `, [ @@ -2970,7 +3054,7 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { ipsInfo.id_ips, autorizanteDefault, solicitanteId, - null, + fechaAutorizacion, observacionFinal, cupCodigo, cie10Codigo, @@ -2980,6 +3064,7 @@ async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) { ambitoAtencion, numeroOrdenFinal, estadoEntrega, + estadoAutorizacion, ] ); diff --git a/saludut-inpec/public/assets/PLANTILLAAUT.xlsx b/saludut-inpec/public/assets/PLANTILLAAUT.xlsx new file mode 100644 index 0000000..00356f4 Binary files /dev/null and b/saludut-inpec/public/assets/PLANTILLAAUT.xlsx differ diff --git a/saludut-inpec/src/app/app.routes.ts b/saludut-inpec/src/app/app.routes.ts index bcd61f0..b1d5bd2 100644 --- a/saludut-inpec/src/app/app.routes.ts +++ b/saludut-inpec/src/app/app.routes.ts @@ -9,6 +9,7 @@ import { UsuariosComponent } from './components/usuarios/usuarios'; import { CargarCupsComponent } from './components/cargar-cups/cargar-cups'; import { EstadisticasAutorizacionesComponent } from './components/estadisticas-autorizaciones/estadisticas-autorizaciones'; import { CargarAutorizacionesMasivasComponent } from './components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas'; +import { CargarAutorizadosMasivosComponent } from './components/cargar-autorizados-masivos/cargar-autorizados-masivos'; import { CargarIpsRepsComponent } from './components/cargar-ips-reps/cargar-ips-reps'; import { CargarPacientesComponent } from './components/cargar-pacientes/cargar-pacientes'; @@ -67,6 +68,12 @@ export const routes: Routes = [ canActivate: [AuthGuard], }, + { + path: 'cargar-autorizados-masivos', + component: CargarAutorizadosMasivosComponent, + canActivate: [AuthGuard, AdminGuard], + }, + { path: 'cargar-ips-reps', component: CargarIpsRepsComponent, diff --git a/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.css b/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.css new file mode 100644 index 0000000..54da4b3 --- /dev/null +++ b/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.css @@ -0,0 +1,117 @@ +/* ============================ + Carga masiva autorizados + ============================ */ +.masivas-container { + display: flex; + flex-direction: column; + gap: 20px; +} + +.masivas-card { + padding: 20px; +} + +.masivas-card h2 { + margin: 0 0 6px; + font-size: 1.5rem; +} + +.subtitle { + margin: 0 0 16px; + color: var(--color-text-muted); + font-size: 0.95rem; +} + +.form-row { + display: flex; + flex-direction: column; + gap: 6px; + margin-bottom: 14px; +} + +.form-row label { + font-weight: 600; + font-size: 0.9rem; +} + +.form-row input[type="file"] { + font-size: 0.9rem; +} + +.file-name { + font-size: 0.85rem; + color: var(--color-text-main); +} + +.form-row.acciones { + margin-top: 8px; +} + +.template-row { + align-items: flex-start; +} + +.template-note { + font-size: 0.85rem; + color: var(--color-text-muted); +} + +.resumen-card h2 { + margin: 0 0 12px; + font-size: 1.4rem; +} + +.summary-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); + gap: 12px; +} + +.summary-item { + background: var(--color-cup-bg); + border: 1px solid var(--color-border); + border-radius: 10px; + padding: 12px; + display: flex; + flex-direction: column; + gap: 6px; +} + +.summary-label { + font-size: 0.8rem; + color: var(--color-text-muted); + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.5px; +} + +.summary-value { + font-size: 1.2rem; + font-weight: 700; + color: var(--color-text-main); +} + +.summary-note { + margin-top: 12px; + padding: 10px 12px; + border-radius: 8px; + background: var(--color-primary-soft); + border: 1px solid var(--color-border); + font-size: 0.9rem; +} + +.error-list { + margin-top: 16px; +} + +.error-list h3 { + margin: 0 0 8px; + font-size: 1rem; +} + +.error-table { + border: 1px solid var(--color-border); + border-radius: 8px; + max-height: 260px; + overflow: auto; +} diff --git a/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.html b/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.html new file mode 100644 index 0000000..a222083 --- /dev/null +++ b/saludut-inpec/src/app/components/cargar-autorizados-masivos/cargar-autorizados-masivos.html @@ -0,0 +1,132 @@ +
+ Usa la plantilla oficial de autorizados. La fecha de registro se guarda como fecha de autorizacion. +
+ +| Fila | +Error | +
|---|---|
| {{ e.fila || '-' }} | +{{ e.error || 'Error en fila' }} | +
Subir plantilla Excel para crear autorizaciones pendientes
+ +Subir plantilla Excel de autorizados
+