cambios finales
This commit is contained in:
parent
d611cec040
commit
5844cfe523
@ -1,47 +1,47 @@
|
|||||||
-- INSERTS TABLA ESTABLECIMIENTO
|
-- UPSERTS TABLA ESTABLECIMIENTO
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('148', 'CPMS ACACIAS', 'ACACIAS', 'META', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('148', 'CPMS ACACIAS', 'ACACIAS', 'META', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('113', 'COMPLEJO CARCELARIO Y PENITENCIARIO BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('113', 'COMPLEJO CARCELARIO Y PENITENCIARIO BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('156', 'PMS LA ESPERANZA DE GUADUAS', 'GUADUAS', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('156', 'PMS LA ESPERANZA DE GUADUAS', 'GUADUAS', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('129', 'CPAMSM BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('129', 'CPAMSM BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('130', 'CPOMS ACACIAS', 'ACACIAS', 'META', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('130', 'CPOMS ACACIAS', 'ACACIAS', 'META', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('114', 'CPMS BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('114', 'CPMS BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('145', 'CPMS ESPINAL', 'ESPINAL', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('145', 'CPMS ESPINAL', 'ESPINAL', 'TOLIMA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('150', 'CPAMS EL BARNE', 'COMBITA', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('150', 'CPAMS EL BARNE', 'COMBITA', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('112', 'EPMSC SOGAMOSO', 'SOGAMOSO', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('112', 'EPMSC SOGAMOSO', 'SOGAMOSO', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('153', 'CPMS YOPAL', 'YOPAL', 'CASANARE', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('153', 'CPMS YOPAL', 'YOPAL', 'CASANARE', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('107', 'EPMSC GUATEQUE', 'GUATEQUE', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('107', 'EPMSC GUATEQUE', 'GUATEQUE', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('157', 'PMS LAS HELICONIAS DE FLORENCIA', 'FLORENCIA', 'CAQUETA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('157', 'PMS LAS HELICONIAS DE FLORENCIA', 'FLORENCIA', 'CAQUETA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('131', 'CPMS VILLAVICENCIO', 'VILLAVICENCIO', 'META', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('131', 'CPMS VILLAVICENCIO', 'VILLAVICENCIO', 'META', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('124', 'CPMS LA MESA', 'LA MESA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('124', 'CPMS LA MESA', 'LA MESA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('133', 'EPMSC GRANADA', 'GRANADA', 'META', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('133', 'EPMSC GRANADA', 'GRANADA', 'META', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('138', 'CPMS GIRARDOT', 'GIRARDOT', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('138', 'CPMS GIRARDOT', 'GIRARDOT', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('142', 'EPMSC PITALITO', 'PITALITO', 'HUILA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('142', 'EPMSC PITALITO', 'PITALITO', 'HUILA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('116', 'EPMSC CAQUEZA', 'CAQUEZA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('116', 'EPMSC CAQUEZA', 'CAQUEZA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('105', 'EPMSC DUITAMA', 'DUITAMA', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('105', 'EPMSC DUITAMA', 'DUITAMA', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('139', 'CPMS NEIVA', 'NEIVA', 'HUILA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('139', 'CPMS NEIVA', 'NEIVA', 'HUILA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('143', 'CPMS FLORENCIA', 'FLORENCIA', 'CAQUETA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('143', 'CPMS FLORENCIA', 'FLORENCIA', 'CAQUETA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('158', 'CPMS GUAMO', 'GUAMO', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('158', 'CPMS GUAMO', 'GUAMO', 'TOLIMA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('127', 'CPMS VILLETA', 'VILLETA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('127', 'CPMS VILLETA', 'VILLETA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('103', 'EPMSC SANTA ROSA DE VITERBO (JYP-MUJERES)', 'SANTA ROSA DE VITERBO', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('103', 'EPMSC SANTA ROSA DE VITERBO (JYP-MUJERES)', 'SANTA ROSA DE VITERBO', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('104', 'CPMS CHIQUINQUIRA', 'CHIQUINQUIRA', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('104', 'CPMS CHIQUINQUIRA', 'CHIQUINQUIRA', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('141', 'CPMS LA PLATA', 'LA PLATA', 'HUILA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('141', 'CPMS LA PLATA', 'LA PLATA', 'HUILA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('140', 'CPMS GARZON', 'GARZON', 'HUILA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('140', 'CPMS GARZON', 'GARZON', 'HUILA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('144', 'EPMSC CHAPARRAL', 'CHAPARRAL', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('144', 'EPMSC CHAPARRAL', 'CHAPARRAL', 'TOLIMA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('120', 'CPMS GACHETA', 'GACHETA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('120', 'CPMS GACHETA', 'GACHETA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('126', 'CPMS UBATE', 'UBATE', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('126', 'CPMS UBATE', 'UBATE', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('119', 'CPMS FUSAGASUGA', 'FUSAGASUGA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('119', 'CPMS FUSAGASUGA', 'FUSAGASUGA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('117', 'CPMS CHOCONTA', 'CHOCONTA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('117', 'CPMS CHOCONTA', 'CHOCONTA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('110', 'CPMS RAMIRIQUI', 'RAMIRIQUI', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('110', 'CPMS RAMIRIQUI', 'RAMIRIQUI', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('109', 'CPMS MONIQUIRA', 'MONIQUIRA', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('109', 'CPMS MONIQUIRA', 'MONIQUIRA', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('149', 'CPMS TUNJA', 'TUNJA', 'BOYACA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('149', 'CPMS TUNJA', 'TUNJA', 'BOYACA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('101', 'EPMSC LETICIA', 'LETICIA', 'AMAZONAS', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('101', 'EPMSC LETICIA', 'LETICIA', 'AMAZONAS', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9020', 'CPAMSEJAPI', 'META', 'REPUBLICA DE COLOMBIA', 'EJERCITO', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9020', 'CPAMSEJAPI', 'META', 'REPUBLICA DE COLOMBIA', 'EJERCITO', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('152', 'CPMS PAZ DE ARIPORO', 'PAZ DE ARIPORO', 'CASANARE', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('152', 'CPMS PAZ DE ARIPORO', 'PAZ DE ARIPORO', 'CASANARE', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9001', 'CPMMSF FACATATIVA', 'FACATATIVA', 'CUNDINAMARCA', 'POLICIA', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9001', 'CPMMSF FACATATIVA', 'FACATATIVA', 'CUNDINAMARCA', 'POLICIA', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('136', 'CPMS MELGAR', 'MELGAR', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('136', 'CPMS MELGAR', 'MELGAR', 'TOLIMA', 'CENTRAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9011', 'CPAMSEJART', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'EJERCITO', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9011', 'CPAMSEJART', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'EJERCITO', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9016', 'CPAMSEJECO', 'FACATATIVA', 'CUNDINAMARCA', 'EJERCITO', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9016', 'CPAMSEJECO', 'FACATATIVA', 'CUNDINAMARCA', 'EJERCITO', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9018', 'CPAMSEJEYO', 'YOPAL', 'CASANARE', 'EJERCITO', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9018', 'CPAMSEJEYO', 'YOPAL', 'CASANARE', 'EJERCITO', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9006', 'CPAMSEJEPO', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'EJERCITO', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9006', 'CPAMSEJEPO', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'EJERCITO', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9022', 'ARBOG BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'ARMADA NACIONAL', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9022', 'ARBOG BOGOTA', 'BOGOTA D.C.', 'BOGOTA DISTRITO CAPITAL', 'ARMADA NACIONAL', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9033', 'CPMMS FUERZA AEREA', 'VILLAVICENCIO', 'META', 'FUERZA AEREA', 'CENTRAL');
|
INSERT INTO establecimiento (codigo_establecimiento, nombre_establecimiento, epc_ciudad, epc_departamento, regional, regional_normalizada) VALUES ('9033', 'CPMMS FUERZA AEREA', 'VILLAVICENCIO', 'META', 'FUERZA AEREA', 'CENTRAL') ON CONFLICT (codigo_establecimiento) DO UPDATE SET nombre_establecimiento = EXCLUDED.nombre_establecimiento, epc_ciudad = EXCLUDED.epc_ciudad, epc_departamento = EXCLUDED.epc_departamento, regional = EXCLUDED.regional, regional_normalizada = EXCLUDED.regional_normalizada;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
-- UPSERTS TABLA INGRESO
|
-- UPSERTS TABLA INGRESO
|
||||||
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('372', '148', 'INTRAMURAL', '2021-09-17', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('372', '148', 'INTRAMURAL', '2021-09-17', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
||||||
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('385', '148', 'INTRAMURAL', '2022-09-17', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
|
||||||
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('394', '113', 'INTRAMURAL', '2019-12-06', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('394', '113', 'INTRAMURAL', '2019-12-06', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
||||||
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('557', '113', 'INTRAMURAL', '2025-08-20', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('557', '113', 'INTRAMURAL', '2025-08-20', 'REPUBLICA DE COLOMBIA') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
||||||
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('833', '113', 'INTRAMURAL', '2019-12-30', 'NO DATO SISIPEC') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
INSERT INTO ingreso (interno, codigo_establecimiento, estado, fecha_ingreso, nacionalidad) VALUES ('833', '113', 'INTRAMURAL', '2019-12-30', 'NO DATO SISIPEC') ON CONFLICT (interno) DO UPDATE SET codigo_establecimiento = EXCLUDED.codigo_establecimiento, estado = EXCLUDED.estado, fecha_ingreso = EXCLUDED.fecha_ingreso, nacionalidad = EXCLUDED.nacionalidad;
|
||||||
|
|||||||
BIN
backend/src/ips.xlsx
Normal file
BIN
backend/src/ips.xlsx
Normal file
Binary file not shown.
@ -1,7 +1,6 @@
|
|||||||
-- UPSERTS TABLA PACIENTE
|
-- UPSERTS TABLA PACIENTE
|
||||||
UPDATE paciente SET activo = false;
|
UPDATE paciente SET activo = false;
|
||||||
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('372', 'CC', '79427056', 'MATEUS', 'RODRIGUEZ', 'ALBEIRO', NULL, '1967-09-01', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('372', 'CC', '79427056', 'MATEUS', 'RODRIGUEZ', 'ALBEIRO', NULL, '1967-09-01', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
||||||
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('385', 'CC', '1013105461', 'GUEVARA', 'RAMIREZ', 'JHONATHAN', 'DAVID', '1983-09-05', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
|
||||||
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('394', 'CC', '15371570', 'TUBERQUIA', 'GONZALEZ', 'JORGE', 'IVAN', '1984-09-05', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('394', 'CC', '15371570', 'TUBERQUIA', 'GONZALEZ', 'JORGE', 'IVAN', '1984-09-05', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
||||||
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('557', 'CC', '80253043', 'MEJIA', 'HUERTAS', 'ANDRES', 'FRANCISCO', '1983-03-16', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('557', 'CC', '80253043', 'MEJIA', 'HUERTAS', 'ANDRES', 'FRANCISCO', '1983-03-16', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
||||||
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('833', 'CC', '75076842', 'GIRALDO', 'CASTAÑO', 'FRANCISCO', 'IVAN', '1975-02-25', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
INSERT INTO paciente (interno, tipo_documento, numero_documento, primer_apellido, segundo_apellido, primer_nombre, segundo_nombre, fecha_nacimiento, edad, sexo, activo) VALUES ('833', 'CC', '75076842', 'GIRALDO', 'CASTAÑO', 'FRANCISCO', 'IVAN', '1975-02-25', NULL, 'MASCULINO', true) ON CONFLICT (interno) DO UPDATE SET tipo_documento = EXCLUDED.tipo_documento, numero_documento = EXCLUDED.numero_documento, primer_apellido = EXCLUDED.primer_apellido, segundo_apellido = EXCLUDED.segundo_apellido, primer_nombre = EXCLUDED.primer_nombre, segundo_nombre = EXCLUDED.segundo_nombre, fecha_nacimiento = EXCLUDED.fecha_nacimiento, sexo = EXCLUDED.sexo, activo = true;
|
||||||
|
|||||||
Binary file not shown.
BIN
backend/src/plantilla.xlsx
Normal file
BIN
backend/src/plantilla.xlsx
Normal file
Binary file not shown.
BIN
backend/src/reps.xlsx
Normal file
BIN
backend/src/reps.xlsx
Normal file
Binary file not shown.
@ -49,7 +49,8 @@ CREATE TABLE IF NOT EXISTS ips (
|
|||||||
direccion text,
|
direccion text,
|
||||||
telefono text,
|
telefono text,
|
||||||
departamento text,
|
departamento text,
|
||||||
municipio text
|
municipio text,
|
||||||
|
tiene_convenio boolean DEFAULT true
|
||||||
);
|
);
|
||||||
|
|
||||||
-- ===== Ingreso =====
|
-- ===== Ingreso =====
|
||||||
@ -91,7 +92,8 @@ CREATE TABLE IF NOT EXISTS usuario (
|
|||||||
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
ultimo_login TIMESTAMP,
|
ultimo_login TIMESTAMP,
|
||||||
intentos_fallidos INTEGER DEFAULT 0,
|
intentos_fallidos INTEGER DEFAULT 0,
|
||||||
bloqueado_hasta TIMESTAMP
|
bloqueado_hasta TIMESTAMP,
|
||||||
|
token_version INTEGER NOT NULL DEFAULT 1
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS usuario_sede (
|
CREATE TABLE IF NOT EXISTS usuario_sede (
|
||||||
@ -163,9 +165,23 @@ CREATE TABLE IF NOT EXISTS autorizacion (
|
|||||||
cup_codigo varchar(20),
|
cup_codigo varchar(20),
|
||||||
tipo_autorizacion varchar(50) NOT NULL DEFAULT 'consultas_externas',
|
tipo_autorizacion varchar(50) NOT NULL DEFAULT 'consultas_externas',
|
||||||
tipo_servicio varchar(50),
|
tipo_servicio varchar(50),
|
||||||
|
estado_autorizacion varchar(20) NOT NULL DEFAULT 'pendiente',
|
||||||
version integer NOT NULL DEFAULT 1
|
version integer NOT NULL DEFAULT 1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM pg_constraint
|
||||||
|
WHERE conname = 'autorizacion_estado_autorizacion_chk'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE autorizacion
|
||||||
|
ADD CONSTRAINT autorizacion_estado_autorizacion_chk
|
||||||
|
CHECK (estado_autorizacion IN ('pendiente', 'autorizado', 'no_autorizado'));
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
INSERT INTO consecutivo_autorizacion (id, codigo)
|
INSERT INTO consecutivo_autorizacion (id, codigo)
|
||||||
VALUES (1, COALESCE((SELECT MAX(numero_autorizacion) FROM autorizacion), 'UTUSCPGB00'))
|
VALUES (1, COALESCE((SELECT MAX(numero_autorizacion) FROM autorizacion), 'UTUSCPGB00'))
|
||||||
ON CONFLICT (id) DO NOTHING;
|
ON CONFLICT (id) DO NOTHING;
|
||||||
@ -201,6 +217,19 @@ CREATE TABLE IF NOT EXISTS autorizacion_version (
|
|||||||
fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
|
fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
-- ===== Profesionales REPS =====
|
||||||
|
CREATE TABLE IF NOT EXISTS profesional_reps (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
nit TEXT,
|
||||||
|
nombre_profesional TEXT,
|
||||||
|
codigo_habilitacion TEXT,
|
||||||
|
direccion TEXT,
|
||||||
|
telefono TEXT,
|
||||||
|
departamento TEXT,
|
||||||
|
municipio TEXT,
|
||||||
|
activo boolean DEFAULT true
|
||||||
|
);
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -55,6 +55,9 @@
|
|||||||
},
|
},
|
||||||
"serve": {
|
"serve": {
|
||||||
"builder": "@angular/build:dev-server",
|
"builder": "@angular/build:dev-server",
|
||||||
|
"options": {
|
||||||
|
"proxyConfig": "proxy.conf.json"
|
||||||
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
"production": {
|
"production": {
|
||||||
"buildTarget": "saludut-inpec:build:production"
|
"buildTarget": "saludut-inpec:build:production"
|
||||||
|
|||||||
7
saludut-inpec/proxy.conf.json
Normal file
7
saludut-inpec/proxy.conf.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"/api": {
|
||||||
|
"target": "http://localhost:3000",
|
||||||
|
"secure": false,
|
||||||
|
"changeOrigin": true
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,6 +7,10 @@ import { AutorizacionesPorFechaComponent } from './components/autorizaciones-por
|
|||||||
import { AutorizacionesComponent } from './components/autorizaciones/autorizaciones';
|
import { AutorizacionesComponent } from './components/autorizaciones/autorizaciones';
|
||||||
import { UsuariosComponent } from './components/usuarios/usuarios';
|
import { UsuariosComponent } from './components/usuarios/usuarios';
|
||||||
import { CargarCupsComponent } from './components/cargar-cups/cargar-cups';
|
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 { CargarIpsRepsComponent } from './components/cargar-ips-reps/cargar-ips-reps';
|
||||||
|
import { CargarPacientesComponent } from './components/cargar-pacientes/cargar-pacientes';
|
||||||
|
|
||||||
|
|
||||||
export const routes: Routes = [
|
export const routes: Routes = [
|
||||||
@ -30,6 +34,12 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'autorizaciones-por-fecha',
|
path: 'autorizaciones-por-fecha',
|
||||||
component: AutorizacionesPorFechaComponent,
|
component: AutorizacionesPorFechaComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'estadisticas-autorizaciones',
|
||||||
|
component: EstadisticasAutorizacionesComponent,
|
||||||
canActivate: [AuthGuard, AdminGuard],
|
canActivate: [AuthGuard, AdminGuard],
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -45,6 +55,24 @@ export const routes: Routes = [
|
|||||||
canActivate: [AuthGuard, AdminGuard],
|
canActivate: [AuthGuard, AdminGuard],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'cargar-pacientes',
|
||||||
|
component: CargarPacientesComponent,
|
||||||
|
canActivate: [AuthGuard, AdminGuard],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'cargar-autorizaciones-masivas',
|
||||||
|
component: CargarAutorizacionesMasivasComponent,
|
||||||
|
canActivate: [AuthGuard],
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
path: 'cargar-ips-reps',
|
||||||
|
component: CargarIpsRepsComponent,
|
||||||
|
canActivate: [AuthGuard, AdminGuard],
|
||||||
|
},
|
||||||
|
|
||||||
// cualquier cosa rara → dashboard
|
// cualquier cosa rara → dashboard
|
||||||
{ path: '**', redirectTo: 'dashboard' },
|
{ path: '**', redirectTo: 'dashboard' },
|
||||||
|
|
||||||
|
|||||||
@ -12,15 +12,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.alert-error {
|
.alert-error {
|
||||||
background: #fef2f2;
|
background: var(--color-permission-no-bg);
|
||||||
border-left: 4px solid #dc2626;
|
border-left: 4px solid var(--color-error);
|
||||||
color: #dc2626;
|
color: var(--color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-success {
|
.alert-success {
|
||||||
background: #f0fdf4;
|
background: var(--color-permission-yes-bg);
|
||||||
border-left: 4px solid #16a34a;
|
border-left: 4px solid var(--color-success);
|
||||||
color: #16a34a;
|
color: var(--color-success);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-icon {
|
.alert-icon {
|
||||||
@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
.filtros-card h2 {
|
.filtros-card h2 {
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@ -83,18 +83,19 @@
|
|||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input {
|
.form-group input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 12px 16px;
|
padding: 12px 16px;
|
||||||
border: 2px solid #e5e7eb;
|
border: 2px solid var(--color-input-border);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
font-size: 0.95rem;
|
font-size: 0.95rem;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
background-color: #ffffff;
|
background-color: var(--color-input-bg);
|
||||||
|
color: var(--color-text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-group input:focus {
|
.form-group input:focus {
|
||||||
@ -104,12 +105,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.form-group input.error {
|
.form-group input.error {
|
||||||
border-color: #dc2626;
|
border-color: var(--color-error);
|
||||||
background-color: #fef2f2;
|
background-color: var(--color-permission-no-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.error-message {
|
.error-message {
|
||||||
color: #dc2626;
|
color: var(--color-error);
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -167,7 +168,7 @@
|
|||||||
|
|
||||||
.resultados-header h2 {
|
.resultados-header h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
@ -175,13 +176,75 @@
|
|||||||
.resultados-actions {
|
.resultados-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero input {
|
||||||
|
min-width: 200px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero input:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1976d2;
|
||||||
|
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-masivo {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-masivo label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-masivo select {
|
||||||
|
min-width: 160px;
|
||||||
|
padding: 8px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-masivo button {
|
||||||
|
min-width: 180px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Table */
|
/* Table */
|
||||||
.table-container {
|
.table-container {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid #e5e7eb;
|
border: 1px solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autorizaciones-table {
|
.autorizaciones-table {
|
||||||
@ -197,23 +260,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.autorizaciones-table tr:hover td {
|
.autorizaciones-table tr:hover td {
|
||||||
background: #f8fafc;
|
background: var(--color-table-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.autorizaciones-table tr.even-row td {
|
.autorizaciones-table tr.even-row td {
|
||||||
background: #fafbfc;
|
background: var(--color-table-row-alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
.numero-autorizacion {
|
.numero-autorizacion {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #1976d2;
|
color: var(--color-primary);
|
||||||
font-family: "Courier New", monospace;
|
font-family: "Courier New", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fecha {
|
.fecha {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.interno {
|
.interno {
|
||||||
@ -227,6 +290,29 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cup-cobertura {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cobertura-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cobertura-ok {
|
||||||
|
background: var(--color-permission-yes-bg);
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cobertura-no {
|
||||||
|
background: var(--color-permission-no-bg);
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
.cup-nivel {
|
.cup-nivel {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -235,15 +321,58 @@
|
|||||||
.nombre-paciente,
|
.nombre-paciente,
|
||||||
.ips,
|
.ips,
|
||||||
.autorizante,
|
.autorizante,
|
||||||
.establecimiento {
|
.establecimiento,
|
||||||
|
.estado {
|
||||||
max-width: 180px;
|
max-width: 180px;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ips-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ips-convenio {
|
||||||
|
align-self: flex-start;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado select {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 140px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-label {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--color-surface-muted);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: capitalize;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-status {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.municipio,
|
.municipio,
|
||||||
.departamento {
|
.departamento {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.acciones {
|
.acciones {
|
||||||
@ -251,6 +380,13 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.no-result {
|
||||||
|
text-align: center;
|
||||||
|
padding: 24px 16px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
.btn-descargar:hover:not(:disabled) {
|
.btn-descargar:hover:not(:disabled) {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
}
|
}
|
||||||
@ -269,14 +405,14 @@
|
|||||||
|
|
||||||
.empty-state h3 {
|
.empty-state h3 {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state p {
|
.empty-state p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
@ -338,10 +474,24 @@
|
|||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero {
|
||||||
|
width: 100%;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resultados-filtro-numero input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.resultados-actions {
|
.resultados-actions {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.estado-masivo {
|
||||||
|
width: 100%;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.autorizaciones-table th,
|
.autorizaciones-table th,
|
||||||
.autorizaciones-table td {
|
.autorizaciones-table td {
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@ -350,7 +500,8 @@
|
|||||||
.nombre-paciente,
|
.nombre-paciente,
|
||||||
.ips,
|
.ips,
|
||||||
.autorizante,
|
.autorizante,
|
||||||
.establecimiento {
|
.establecimiento,
|
||||||
|
.estado {
|
||||||
max-width: 120px;
|
max-width: 120px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -361,10 +512,23 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-exportar,
|
.btn-exportar,
|
||||||
.btn-descargar-todos {
|
.btn-descargar-todos,
|
||||||
|
.estado-masivo,
|
||||||
|
.estado-masivo button {
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.estado-masivo {
|
||||||
|
width: 100%;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.estado-masivo select,
|
||||||
|
.estado-masivo button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
padding: 40px 16px;
|
padding: 40px 16px;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,10 +3,15 @@
|
|||||||
<app-header
|
<app-header
|
||||||
title="Autorizaciones por fecha"
|
title="Autorizaciones por fecha"
|
||||||
subtitle="Consulta y descarga de autorizaciones por rango de fechas"
|
subtitle="Consulta y descarga de autorizaciones por rango de fechas"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="isLoggedIn()"
|
||||||
|
[userName]="getCurrentUser()?.nombre_completo"
|
||||||
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
|
[showLogout]="isLoggedIn()"
|
||||||
|
(logout)="logout()"
|
||||||
[showBack]="true"
|
[showBack]="true"
|
||||||
backLabel="Volver"
|
backLabel="Volver"
|
||||||
(back)="volverAtras()"
|
(back)="volverAtras()"
|
||||||
[showLogo]="false"
|
|
||||||
></app-header>
|
></app-header>
|
||||||
|
|
||||||
<!-- Mensajes -->
|
<!-- Mensajes -->
|
||||||
@ -75,8 +80,44 @@
|
|||||||
<!-- Resultados -->
|
<!-- Resultados -->
|
||||||
<div class="resultados-section card" *ngIf="hayResultados || autorizaciones.length > 0">
|
<div class="resultados-section card" *ngIf="hayResultados || autorizaciones.length > 0">
|
||||||
<div class="resultados-header">
|
<div class="resultados-header">
|
||||||
<h2>Resultados ({{ autorizaciones.length }} autorizaciones)</h2>
|
<h2>
|
||||||
<div class="resultados-actions">
|
Resultados ({{ autorizacionesFiltradas.length }} autorizaciones
|
||||||
|
<span *ngIf="filtroNumero.trim().length > 0">
|
||||||
|
de {{ autorizaciones.length }}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
</h2>
|
||||||
|
<div class="resultados-filtro-numero">
|
||||||
|
<label for="filtroNumero">Buscar numero:</label>
|
||||||
|
<input
|
||||||
|
id="filtroNumero"
|
||||||
|
type="text"
|
||||||
|
[ngModel]="filtroNumero"
|
||||||
|
(ngModelChange)="onFiltroNumeroChange($event)"
|
||||||
|
placeholder="Numero de autorizacion"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="resultados-actions" *ngIf="esAdmin">
|
||||||
|
<div class="estado-masivo">
|
||||||
|
<label for="estadoMasivo">Estado masivo:</label>
|
||||||
|
<select
|
||||||
|
id="estadoMasivo"
|
||||||
|
[(ngModel)]="estadoMasivo"
|
||||||
|
[disabled]="actualizandoMasivo"
|
||||||
|
>
|
||||||
|
<option value="pendiente">Pendiente</option>
|
||||||
|
<option value="autorizado">Autorizado</option>
|
||||||
|
<option value="no_autorizado">No autorizado</option>
|
||||||
|
</select>
|
||||||
|
<button
|
||||||
|
class="btn btn-secondary"
|
||||||
|
type="button"
|
||||||
|
(click)="aplicarEstadoMasivo()"
|
||||||
|
[disabled]="actualizandoMasivo || autorizaciones.length === 0"
|
||||||
|
>
|
||||||
|
{{ actualizandoMasivo ? 'Actualizando...' : 'Aplicar a todo el rango' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
class="btn btn-secondary btn-exportar"
|
class="btn btn-secondary btn-exportar"
|
||||||
(click)="exportarAExcel()"
|
(click)="exportarAExcel()"
|
||||||
@ -109,18 +150,25 @@
|
|||||||
<th>Tipo autorizacion</th>
|
<th>Tipo autorizacion</th>
|
||||||
<th>Tipo servicio</th>
|
<th>Tipo servicio</th>
|
||||||
<th>CUPS</th>
|
<th>CUPS</th>
|
||||||
|
<th>Cubre</th>
|
||||||
<th>Nivel</th>
|
<th>Nivel</th>
|
||||||
<th>IPS</th>
|
<th>IPS</th>
|
||||||
<th>Municipio</th>
|
<th>Municipio</th>
|
||||||
<th>Departamento</th>
|
<th>Departamento</th>
|
||||||
<th>Autorizante</th>
|
<th>Autorizante</th>
|
||||||
<th>Establecimiento</th>
|
<th>Establecimiento</th>
|
||||||
|
<th>Estado</th>
|
||||||
<th>Acciones</th>
|
<th>Acciones</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr *ngIf="autorizacionesFiltradas.length === 0">
|
||||||
|
<td class="no-result" colspan="17">
|
||||||
|
No hay autorizaciones para ese numero.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
<tr
|
<tr
|
||||||
*ngFor="let aut of autorizaciones; let i = index; trackBy: trackByAutorizacion"
|
*ngFor="let aut of autorizacionesFiltradas; let i = index; trackBy: trackByAutorizacion"
|
||||||
[class.even-row]="i % 2 === 0"
|
[class.even-row]="i % 2 === 0"
|
||||||
>
|
>
|
||||||
<td class="numero-autorizacion">
|
<td class="numero-autorizacion">
|
||||||
@ -137,18 +185,61 @@
|
|||||||
{{ getTipoServicioLabel(aut.tipo_servicio) }}
|
{{ getTipoServicioLabel(aut.tipo_servicio) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="cup-codigo">{{ aut.cup_codigo }}</td>
|
<td class="cup-codigo">{{ aut.cup_codigo }}</td>
|
||||||
|
<td class="cup-cobertura">
|
||||||
|
<span
|
||||||
|
class="cobertura-badge"
|
||||||
|
[class.cobertura-ok]="esCupCubierto(aut)"
|
||||||
|
[class.cobertura-no]="!esCupCubierto(aut)"
|
||||||
|
>
|
||||||
|
{{ getCoberturaLabel(aut) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
<td class="cup-nivel">{{ aut.cup_nivel }}</td>
|
<td class="cup-nivel">{{ aut.cup_nivel }}</td>
|
||||||
<td class="ips">{{ aut.nombre_ips }}</td>
|
<td class="ips">
|
||||||
<td class="municipio">{{ aut.municipio }}</td>
|
<div class="ips-info">
|
||||||
<td class="departamento">{{ aut.departamento }}</td>
|
<span>{{ aut.nombre_ips }}</span>
|
||||||
|
<span
|
||||||
|
class="badge badge-danger ips-convenio"
|
||||||
|
*ngIf="aut.ips_tiene_convenio === false"
|
||||||
|
>
|
||||||
|
No cubre IPS
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class="municipio">{{ aut.municipio || '-' }}</td>
|
||||||
|
<td class="departamento">{{ aut.departamento || '-' }}</td>
|
||||||
<td class="autorizante">{{ aut.nombre_autorizante }}</td>
|
<td class="autorizante">{{ aut.nombre_autorizante }}</td>
|
||||||
<td class="establecimiento">{{ aut.nombre_establecimiento }}</td>
|
<td class="establecimiento">{{ aut.nombre_establecimiento }}</td>
|
||||||
|
<td class="estado">
|
||||||
|
<ng-container *ngIf="esAdmin; else estadoTexto">
|
||||||
|
<select
|
||||||
|
[ngModel]="aut.estado_autorizacion || 'pendiente'"
|
||||||
|
(ngModelChange)="actualizarEstadoAutorizacion(aut, $event)"
|
||||||
|
[disabled]="actualizandoEstado[aut.numero_autorizacion]"
|
||||||
|
>
|
||||||
|
<option value="pendiente">Pendiente</option>
|
||||||
|
<option value="autorizado">Autorizado</option>
|
||||||
|
<option value="no_autorizado">No autorizado</option>
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
class="mini-status"
|
||||||
|
*ngIf="actualizandoEstado[aut.numero_autorizacion]"
|
||||||
|
>
|
||||||
|
Guardando...
|
||||||
|
</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #estadoTexto>
|
||||||
|
<span class="estado-label">
|
||||||
|
{{ getEstadoAutorizacionLabel(aut.estado_autorizacion) }}
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
<td class="acciones">
|
<td class="acciones">
|
||||||
<button
|
<button
|
||||||
class="btn btn-success btn-sm btn-descargar"
|
class="btn btn-success btn-sm btn-descargar"
|
||||||
(click)="descargarPdf(aut.numero_autorizacion)"
|
(click)="descargarPdf(aut)"
|
||||||
title="Descargar PDF"
|
title="Descargar PDF"
|
||||||
[disabled]="descargandoPdf"
|
[disabled]="descargandoPdf || !puedeDescargarPdfAutorizacion(aut)"
|
||||||
>
|
>
|
||||||
PDF
|
PDF
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -1,15 +1,16 @@
|
|||||||
import { ChangeDetectorRef, Component, OnInit, Inject } from '@angular/core';
|
import { ChangeDetectorRef, Component, OnInit, Inject } from '@angular/core';
|
||||||
import { CommonModule, DOCUMENT } from '@angular/common';
|
import { CommonModule, DOCUMENT } from '@angular/common';
|
||||||
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
|
import { FormBuilder, FormGroup, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
||||||
import { finalize } from 'rxjs/operators';
|
import { finalize } from 'rxjs/operators';
|
||||||
import { AuthService } from '../../services/auth';
|
import { AuthService } from '../../services/auth';
|
||||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
import { JobsService } from '../../services/jobs';
|
import { JobsService } from '../../services/jobs';
|
||||||
|
import { PacienteService } from '../../services/paciente';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-autorizaciones-por-fecha',
|
selector: 'app-autorizaciones-por-fecha',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, ReactiveFormsModule, AppHeaderComponent],
|
imports: [CommonModule, ReactiveFormsModule, FormsModule, AppHeaderComponent],
|
||||||
templateUrl: './autorizaciones-por-fecha.html',
|
templateUrl: './autorizaciones-por-fecha.html',
|
||||||
styleUrls: ['./autorizaciones-por-fecha.css']
|
styleUrls: ['./autorizaciones-por-fecha.css']
|
||||||
})
|
})
|
||||||
@ -23,6 +24,12 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
avisoAutorizaciones = false;
|
avisoAutorizaciones = false;
|
||||||
descargandoZip = false;
|
descargandoZip = false;
|
||||||
descargandoPdf = false;
|
descargandoPdf = false;
|
||||||
|
actualizandoEstado: Record<string, boolean> = {};
|
||||||
|
actualizandoMasivo = false;
|
||||||
|
estadoMasivo: 'pendiente' | 'autorizado' | 'no_autorizado' = 'autorizado';
|
||||||
|
esAdmin = false;
|
||||||
|
filtroNumero = '';
|
||||||
|
autorizacionesFiltradas: any[] = [];
|
||||||
|
|
||||||
// Para saber si ya buscamos algo
|
// Para saber si ya buscamos algo
|
||||||
hayResultados = false;
|
hayResultados = false;
|
||||||
@ -35,6 +42,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
private fb: FormBuilder,
|
private fb: FormBuilder,
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private jobsService: JobsService,
|
private jobsService: JobsService,
|
||||||
|
private pacienteService: PacienteService,
|
||||||
private cdr: ChangeDetectorRef,
|
private cdr: ChangeDetectorRef,
|
||||||
@Inject(DOCUMENT) private document: Document
|
@Inject(DOCUMENT) private document: Document
|
||||||
) {
|
) {
|
||||||
@ -45,11 +53,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
// Solo admin puede entrar (además del guard)
|
this.esAdmin = this.authService.isAdministrador();
|
||||||
if (!this.authService.isAdministrador()) {
|
|
||||||
this.errorMessage = 'No tienes permisos para acceder a esta página.';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rango por defecto: últimos 30 días
|
// Rango por defecto: últimos 30 días
|
||||||
const hoy = new Date();
|
const hoy = new Date();
|
||||||
@ -84,6 +88,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
|
|
||||||
this.isLoading = true;
|
this.isLoading = true;
|
||||||
this.autorizaciones = [];
|
this.autorizaciones = [];
|
||||||
|
this.autorizacionesFiltradas = [];
|
||||||
this.hayResultados = false;
|
this.hayResultados = false;
|
||||||
|
|
||||||
this.authService
|
this.authService
|
||||||
@ -100,6 +105,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
.subscribe({
|
.subscribe({
|
||||||
next: (data) => {
|
next: (data) => {
|
||||||
this.autorizaciones = data || [];
|
this.autorizaciones = data || [];
|
||||||
|
this.aplicarFiltroNumero();
|
||||||
this.hayResultados = this.autorizaciones.length > 0;
|
this.hayResultados = this.autorizaciones.length > 0;
|
||||||
this.avisoAutorizaciones =
|
this.avisoAutorizaciones =
|
||||||
this.autorizaciones.length >= this.limiteAutorizaciones;
|
this.autorizaciones.length >= this.limiteAutorizaciones;
|
||||||
@ -117,8 +123,19 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ========= PDF INDIVIDUAL =========
|
// ========= PDF INDIVIDUAL =========
|
||||||
descargarPdf(numeroAutorizacion: string): void {
|
descargarPdf(autorizacion: any): void {
|
||||||
this.limpiarMensajes();
|
this.limpiarMensajes();
|
||||||
|
|
||||||
|
if (!this.puedeDescargarPdfAutorizacion(autorizacion)) {
|
||||||
|
this.errorMessage = 'Autorizacion pendiente o no autorizada.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numeroAutorizacion = String(autorizacion?.numero_autorizacion || '');
|
||||||
|
if (!numeroAutorizacion) {
|
||||||
|
this.errorMessage = 'Numero de autorizacion invalido.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.descargandoPdf = true;
|
this.descargandoPdf = true;
|
||||||
|
|
||||||
this.authService.crearJobPdfAutorizacion(numeroAutorizacion).subscribe({
|
this.authService.crearJobPdfAutorizacion(numeroAutorizacion).subscribe({
|
||||||
@ -173,10 +190,130 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========= ESTADO DE AUTORIZACION =========
|
||||||
|
actualizarEstadoAutorizacion(
|
||||||
|
autorizacion: any,
|
||||||
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
||||||
|
): void {
|
||||||
|
if (!this.esAdmin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!autorizacion) return;
|
||||||
|
|
||||||
|
const numero = String(autorizacion.numero_autorizacion || '');
|
||||||
|
if (!numero) return;
|
||||||
|
|
||||||
|
if (this.actualizandoEstado[numero]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actualizandoEstado[numero] = true;
|
||||||
|
const estadoPrevio = autorizacion.estado_autorizacion || 'pendiente';
|
||||||
|
autorizacion.estado_autorizacion = estado;
|
||||||
|
|
||||||
|
this.pacienteService
|
||||||
|
.actualizarEstadoAutorizacion(numero, estado)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.actualizandoEstado[numero] = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (resp) => {
|
||||||
|
autorizacion.estado_autorizacion =
|
||||||
|
resp?.autorizacion?.estado_autorizacion || estado;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
autorizacion.estado_autorizacion = estadoPrevio;
|
||||||
|
this.errorMessage =
|
||||||
|
err?.error?.error || 'Error actualizando el estado.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
aplicarEstadoMasivo(): void {
|
||||||
|
this.limpiarMensajes();
|
||||||
|
|
||||||
|
if (!this.esAdmin) {
|
||||||
|
this.errorMessage = 'No tienes permisos para actualizar estados.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.fechaInicioApi || !this.fechaFinApi) {
|
||||||
|
this.errorMessage = 'Primero realiza una busqueda por fechas.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const confirmacion = confirm(
|
||||||
|
`¿Seguro que deseas aplicar "${this.getEstadoAutorizacionLabel(this.estadoMasivo)}" a todas las autorizaciones del rango?`
|
||||||
|
);
|
||||||
|
if (!confirmacion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actualizandoMasivo = true;
|
||||||
|
|
||||||
|
this.pacienteService
|
||||||
|
.actualizarEstadoAutorizacionesMasivo(
|
||||||
|
this.fechaInicioApi,
|
||||||
|
this.fechaFinApi,
|
||||||
|
this.estadoMasivo
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (resp) => {
|
||||||
|
const total = resp?.actualizados ?? 0;
|
||||||
|
this.autorizaciones = this.autorizaciones.map((aut) => ({
|
||||||
|
...aut,
|
||||||
|
estado_autorizacion: this.estadoMasivo,
|
||||||
|
}));
|
||||||
|
this.aplicarFiltroNumero();
|
||||||
|
this.successMessage = `Se actualizaron ${total} autorizaciones.`;
|
||||||
|
this.actualizandoMasivo = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorMessage =
|
||||||
|
err?.error?.error || 'Error actualizando estados.';
|
||||||
|
this.actualizandoMasivo = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========= USUARIO =========
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): any {
|
||||||
|
return this.authService.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
puedeDescargarPdfAutorizacion(autorizacion: any): boolean {
|
||||||
|
if (this.esAdmin) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const estado = String(autorizacion?.estado_autorizacion || 'pendiente').toLowerCase();
|
||||||
|
return estado === 'autorizado';
|
||||||
|
}
|
||||||
|
|
||||||
// ========= ZIP CON TODAS LAS AUTORIZACIONES =========
|
// ========= ZIP CON TODAS LAS AUTORIZACIONES =========
|
||||||
descargarTodosLosPdfs(): void {
|
descargarTodosLosPdfs(): void {
|
||||||
this.limpiarMensajes();
|
this.limpiarMensajes();
|
||||||
|
|
||||||
|
if (!this.esAdmin) {
|
||||||
|
this.errorMessage = 'No tienes permisos para descargar todos los PDFs.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.autorizaciones || this.autorizaciones.length === 0) {
|
if (!this.autorizaciones || this.autorizaciones.length === 0) {
|
||||||
this.errorMessage = 'No hay autorizaciones para descargar.';
|
this.errorMessage = 'No hay autorizaciones para descargar.';
|
||||||
return;
|
return;
|
||||||
@ -302,6 +439,50 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
return tipo ? String(tipo) : '';
|
return tipo ? String(tipo) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEstadoAutorizacionLabel(estado: string | null | undefined): string {
|
||||||
|
const normalizado = String(estado || 'pendiente').toLowerCase();
|
||||||
|
if (normalizado === 'autorizado') {
|
||||||
|
return 'Autorizado';
|
||||||
|
}
|
||||||
|
if (normalizado === 'no_autorizado') {
|
||||||
|
return 'No autorizado';
|
||||||
|
}
|
||||||
|
return 'Pendiente';
|
||||||
|
}
|
||||||
|
|
||||||
|
getCoberturaLabel(autorizacion: any): string {
|
||||||
|
if (autorizacion?.cup_cubierto === true) {
|
||||||
|
return 'Cubre';
|
||||||
|
}
|
||||||
|
if (autorizacion?.cup_cubierto === false) {
|
||||||
|
return 'No cubre';
|
||||||
|
}
|
||||||
|
if (autorizacion?.cup_descripcion || autorizacion?.cup_nivel) {
|
||||||
|
return 'Cubre';
|
||||||
|
}
|
||||||
|
return 'No cubre';
|
||||||
|
}
|
||||||
|
|
||||||
|
esCupCubierto(autorizacion: any): boolean {
|
||||||
|
return this.getCoberturaLabel(autorizacion) === 'Cubre';
|
||||||
|
}
|
||||||
|
|
||||||
|
onFiltroNumeroChange(valor: string): void {
|
||||||
|
this.filtroNumero = valor || '';
|
||||||
|
this.aplicarFiltroNumero();
|
||||||
|
}
|
||||||
|
|
||||||
|
private aplicarFiltroNumero(): void {
|
||||||
|
const filtro = this.filtroNumero.trim().toLowerCase();
|
||||||
|
if (!filtro) {
|
||||||
|
this.autorizacionesFiltradas = [...this.autorizaciones];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.autorizacionesFiltradas = this.autorizaciones.filter((aut) =>
|
||||||
|
String(aut?.numero_autorizacion || '').toLowerCase().includes(filtro)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
limpiarMensajes(): void {
|
limpiarMensajes(): void {
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
this.successMessage = null;
|
this.successMessage = null;
|
||||||
@ -347,7 +528,14 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
|
|
||||||
// ========= EXPORTAR A CSV SIMPLE =========
|
// ========= EXPORTAR A CSV SIMPLE =========
|
||||||
exportarAExcel(): void {
|
exportarAExcel(): void {
|
||||||
if (this.autorizaciones.length === 0) {
|
if (!this.esAdmin) {
|
||||||
|
this.errorMessage = 'No tienes permisos para exportar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataset = this.autorizacionesFiltradas;
|
||||||
|
|
||||||
|
if (dataset.length === 0) {
|
||||||
this.errorMessage = 'No hay datos para exportar.';
|
this.errorMessage = 'No hay datos para exportar.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -366,13 +554,14 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
'Municipio',
|
'Municipio',
|
||||||
'Departamento',
|
'Departamento',
|
||||||
'Autorizante',
|
'Autorizante',
|
||||||
'Establecimiento'
|
'Establecimiento',
|
||||||
|
'Estado'
|
||||||
];
|
];
|
||||||
|
|
||||||
const separator = ';';
|
const separator = ';';
|
||||||
const csvContent = [
|
const csvContent = [
|
||||||
headers.map((header) => this.csvValue(header)).join(separator),
|
headers.map((header) => this.csvValue(header)).join(separator),
|
||||||
...this.autorizaciones.map((aut) =>
|
...dataset.map((aut) =>
|
||||||
[
|
[
|
||||||
this.csvValue(aut.numero_autorizacion),
|
this.csvValue(aut.numero_autorizacion),
|
||||||
this.csvValue(aut.version || ''),
|
this.csvValue(aut.version || ''),
|
||||||
@ -387,7 +576,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
|||||||
this.csvValue(aut.municipio),
|
this.csvValue(aut.municipio),
|
||||||
this.csvValue(aut.departamento),
|
this.csvValue(aut.departamento),
|
||||||
this.csvValue(aut.nombre_autorizante),
|
this.csvValue(aut.nombre_autorizante),
|
||||||
this.csvValue(aut.nombre_establecimiento)
|
this.csvValue(aut.nombre_establecimiento),
|
||||||
|
this.csvValue(this.getEstadoAutorizacionLabel(aut.estado_autorizacion))
|
||||||
].join(separator)
|
].join(separator)
|
||||||
)
|
)
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|||||||
@ -121,6 +121,22 @@
|
|||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.cup-status {
|
||||||
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cup-alert {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin: 8px 0 12px;
|
||||||
|
font-size: var(--font-size-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cup-alert-text {
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.upload-excel {
|
.upload-excel {
|
||||||
margin-top: 12px;
|
margin-top: 12px;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -244,12 +260,39 @@
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.estado-cell select {
|
||||||
|
padding: 4px 6px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mini-status {
|
||||||
|
display: block;
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.pdf-restricted {
|
.pdf-restricted {
|
||||||
color: var(--color-text-muted);
|
color: var(--color-text-muted);
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ips-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ips-convenio {
|
||||||
|
align-self: flex-start;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
/* === Modal de autorizacion === */
|
/* === Modal de autorizacion === */
|
||||||
.aut-modal-backdrop {
|
.aut-modal-backdrop {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|||||||
@ -9,6 +9,9 @@
|
|||||||
[userRole]="getCurrentUser()?.nombre_rol"
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
[showLogout]="isLoggedIn()"
|
[showLogout]="isLoggedIn()"
|
||||||
(logout)="logout()"
|
(logout)="logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="irADashboard()"
|
||||||
></app-header>
|
></app-header>
|
||||||
|
|
||||||
<!-- Tarjeta de búsqueda -->
|
<!-- Tarjeta de búsqueda -->
|
||||||
@ -36,31 +39,8 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Botón de cargar Excel (solo administradores) -->
|
|
||||||
<div class="upload-excel" *ngIf="puedeCargarPacientes()">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="btn btn-primary"
|
|
||||||
(click)="inputExcel.click()"
|
|
||||||
[disabled]="cargandoExcel"
|
|
||||||
>
|
|
||||||
{{ cargandoExcel ? 'Cargando Excel...' : 'Cargar Excel de PPL' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<input
|
|
||||||
#inputExcel
|
|
||||||
type="file"
|
|
||||||
accept=".xlsx,.xls"
|
|
||||||
(change)="onExcelSelected($event)"
|
|
||||||
hidden
|
|
||||||
/>
|
|
||||||
|
|
||||||
<span class="upload-msg" *ngIf="estadoCargaExcel">
|
|
||||||
{{ estadoCargaExcel }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Botón de ver autorizaciones por fecha (solo administradores) -->
|
|
||||||
<div class="admin-actions" *ngIf="puedeVerTodasAutorizaciones()">
|
<div class="admin-actions" *ngIf="puedeVerTodasAutorizaciones()">
|
||||||
<button
|
<button
|
||||||
class="btn btn-gradient"
|
class="btn btn-gradient"
|
||||||
@ -69,15 +49,12 @@
|
|||||||
>
|
>
|
||||||
Ver autorizaciones por fecha
|
Ver autorizaciones por fecha
|
||||||
</button>
|
</button>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="nav-actions">
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-secondary"
|
class="btn btn-secondary"
|
||||||
(click)="irADashboard()"
|
(click)="irACargarPacientes()"
|
||||||
title="Volver al dashboard"
|
title="Cargar pacientes"
|
||||||
>
|
>
|
||||||
Volver al dashboard
|
Cargar pacientes
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -205,7 +182,7 @@
|
|||||||
>
|
>
|
||||||
<option value="">-- Seleccione IPS --</option>
|
<option value="">-- Seleccione IPS --</option>
|
||||||
<option *ngFor="let ips of ipsDisponibles" [value]="ips.id_ips">
|
<option *ngFor="let ips of ipsDisponibles" [value]="ips.id_ips">
|
||||||
{{ ips.nombre_ips }} ({{ ips.municipio }} - {{ ips.departamento }})
|
{{ ips.nombre_ips }} ({{ ips.municipio || '-' }} - {{ ips.departamento || '-' }}){{ ips.tiene_convenio === false ? ' - NO CUBRE' : '' }}
|
||||||
</option>
|
</option>
|
||||||
</select>
|
</select>
|
||||||
<button
|
<button
|
||||||
@ -247,6 +224,7 @@
|
|||||||
[(ngModel)]="formAutorizacion.cup_codigo"
|
[(ngModel)]="formAutorizacion.cup_codigo"
|
||||||
placeholder="Codigo o descripcion"
|
placeholder="Codigo o descripcion"
|
||||||
(keyup.enter)="buscarCups()"
|
(keyup.enter)="buscarCups()"
|
||||||
|
(input)="onCupInputChange()"
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@ -275,10 +253,34 @@
|
|||||||
<div class="cup-code">{{ cup.codigo }}</div>
|
<div class="cup-code">{{ cup.codigo }}</div>
|
||||||
<div class="cup-desc">{{ cup.descripcion }}</div>
|
<div class="cup-desc">{{ cup.descripcion }}</div>
|
||||||
<div class="cup-meta" *ngIf="cup.nivel">Nivel {{ cup.nivel }}</div>
|
<div class="cup-meta" *ngIf="cup.nivel">Nivel {{ cup.nivel }}</div>
|
||||||
|
<div class="cup-status">
|
||||||
|
<span
|
||||||
|
class="badge"
|
||||||
|
[ngClass]="cup.cubierto ? 'badge-success' : 'badge-danger'"
|
||||||
|
>
|
||||||
|
{{ cup.cubierto ? 'CUBIERTO' : 'NO CUBIERTO' }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="cup-alert" *ngIf="cupSeleccionado">
|
||||||
|
<span
|
||||||
|
class="badge"
|
||||||
|
[ngClass]="cupSeleccionado.cubierto ? 'badge-success' : 'badge-danger'"
|
||||||
|
>
|
||||||
|
{{ cupSeleccionado.cubierto ? 'CUBIERTO' : 'NO CUBIERTO' }}
|
||||||
|
</span>
|
||||||
|
<span class="cup-alert-text">
|
||||||
|
{{
|
||||||
|
cupSeleccionado.cubierto
|
||||||
|
? 'Este CUPS esta cubierto por la nota tecnica.'
|
||||||
|
: 'Este CUPS no esta cubierto por la nota tecnica.'
|
||||||
|
}}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="status error" *ngIf="errorCups">
|
<div class="status error" *ngIf="errorCups">
|
||||||
{{ errorCups }}
|
{{ errorCups }}
|
||||||
</div>
|
</div>
|
||||||
@ -357,6 +359,7 @@
|
|||||||
<th>Version</th>
|
<th>Version</th>
|
||||||
<th>IPS</th>
|
<th>IPS</th>
|
||||||
<th>Autoriza</th>
|
<th>Autoriza</th>
|
||||||
|
<th>Estado</th>
|
||||||
<th>Acciones</th>
|
<th>Acciones</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -393,8 +396,50 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>{{ a.nombre_ips }}</td>
|
<td>
|
||||||
|
<div class="ips-info">
|
||||||
|
<span>{{ a.nombre_ips }}</span>
|
||||||
|
<span
|
||||||
|
class="badge badge-danger ips-convenio"
|
||||||
|
*ngIf="a.ips_tiene_convenio === false"
|
||||||
|
>
|
||||||
|
No cubre IPS
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
<td>{{ a.nombre_autorizante }}</td>
|
<td>{{ a.nombre_autorizante }}</td>
|
||||||
|
<td class="estado-cell">
|
||||||
|
<ng-container *ngIf="isAdministrador(); else estadoSoloLectura">
|
||||||
|
<select
|
||||||
|
[ngModel]="a.estado_autorizacion || 'pendiente'"
|
||||||
|
(ngModelChange)="actualizarEstadoAutorizacion(a, $event)"
|
||||||
|
>
|
||||||
|
<option value="pendiente">Pendiente</option>
|
||||||
|
<option value="autorizado">Autorizado</option>
|
||||||
|
<option value="no_autorizado">No autorizado</option>
|
||||||
|
</select>
|
||||||
|
<span
|
||||||
|
class="mini-status"
|
||||||
|
*ngIf="actualizandoEstado[a.numero_autorizacion]"
|
||||||
|
>
|
||||||
|
Guardando...
|
||||||
|
</span>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #estadoSoloLectura>
|
||||||
|
<span
|
||||||
|
class="badge"
|
||||||
|
[ngClass]="
|
||||||
|
(a.estado_autorizacion || 'pendiente') === 'autorizado'
|
||||||
|
? 'badge-success'
|
||||||
|
: (a.estado_autorizacion || 'pendiente') === 'no_autorizado'
|
||||||
|
? 'badge-danger'
|
||||||
|
: ''
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ getEstadoAutorizacionLabel(a.estado_autorizacion) }}
|
||||||
|
</span>
|
||||||
|
</ng-template>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="accion-buttons">
|
<div class="accion-buttons">
|
||||||
<button
|
<button
|
||||||
@ -405,15 +450,15 @@
|
|||||||
Editar
|
Editar
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="puedeDescargarPdfs()"
|
*ngIf="puedeDescargarPdfAutorizacion(a)"
|
||||||
class="btn btn-secondary btn-sm"
|
class="btn btn-secondary btn-sm"
|
||||||
[disabled]="descargandoPdf"
|
[disabled]="descargandoPdf"
|
||||||
(click)="descargarPdf(a.numero_autorizacion, getVersionSeleccionada(a.numero_autorizacion, a.version))"
|
(click)="descargarPdf(a, getVersionSeleccionada(a.numero_autorizacion, a.version))"
|
||||||
>
|
>
|
||||||
PDF
|
PDF
|
||||||
</button>
|
</button>
|
||||||
<span *ngIf="!puedeDescargarPdfs()" class="pdf-restricted">
|
<span *ngIf="!puedeDescargarPdfAutorizacion(a)" class="pdf-restricted">
|
||||||
Solo admin
|
Pendiente de aprobacion
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Router } from '@angular/router';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import { AuthService } from '../../services/auth';
|
import { AuthService } from '../../services/auth';
|
||||||
import { PacienteService, AutorizacionVersion } from '../../services/paciente';
|
import { PacienteService, AutorizacionVersion, CupInfo } from '../../services/paciente';
|
||||||
import { finalize } from 'rxjs/operators';
|
import { finalize } from 'rxjs/operators';
|
||||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
import { JobsService } from '../../services/jobs';
|
import { JobsService } from '../../services/jobs';
|
||||||
@ -26,21 +26,19 @@ export class AutorizacionesComponent {
|
|||||||
cargando = false;
|
cargando = false;
|
||||||
error: string | null = null;
|
error: string | null = null;
|
||||||
|
|
||||||
// ---- Excel ----
|
|
||||||
cargandoExcel = false;
|
|
||||||
estadoCargaExcel: string | null = null;
|
|
||||||
|
|
||||||
// ---- Autorizaciones ----
|
// ---- Autorizaciones ----
|
||||||
pacienteSeleccionado: any = null;
|
pacienteSeleccionado: any = null;
|
||||||
ipsDisponibles: any[] = [];
|
ipsDisponibles: any[] = [];
|
||||||
autorizantes: any[] = [];
|
autorizantes: any[] = [];
|
||||||
cupsDisponibles: any[] = [];
|
cupsDisponibles: CupInfo[] = [];
|
||||||
|
cupSeleccionado: CupInfo | null = null;
|
||||||
buscandoCups = false;
|
buscandoCups = false;
|
||||||
errorCups: string | null = null;
|
errorCups: string | null = null;
|
||||||
verMasIps = false;
|
verMasIps = false;
|
||||||
departamentoInterno = '';
|
departamentoInterno = '';
|
||||||
observacionTraslado = '';
|
observacionTraslado = '';
|
||||||
autorizacionEditando: any | null = null;
|
autorizacionEditando: any | null = null;
|
||||||
|
actualizandoEstado: Record<string, boolean> = {};
|
||||||
|
|
||||||
versionesPorAutorizacion: Record<string, AutorizacionVersion[]> = {};
|
versionesPorAutorizacion: Record<string, AutorizacionVersion[]> = {};
|
||||||
versionActualPorAutorizacion: Record<string, number> = {};
|
versionActualPorAutorizacion: Record<string, number> = {};
|
||||||
@ -121,76 +119,6 @@ export class AutorizacionesComponent {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
|
||||||
// Cargar Excel (botón)
|
|
||||||
// -------------------------
|
|
||||||
|
|
||||||
onExcelSelected(event: Event): void {
|
|
||||||
const input = event.target as HTMLInputElement;
|
|
||||||
const file = input.files?.[0];
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cargandoExcel = true;
|
|
||||||
this.estadoCargaExcel = 'Subiendo archivo...';
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('archivo', file);
|
|
||||||
|
|
||||||
this.pacienteService.cargarExcelPacientes(formData).subscribe({
|
|
||||||
next: (job) => {
|
|
||||||
this.estadoCargaExcel = 'Archivo en cola. Procesando...';
|
|
||||||
if (input) {
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
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?.activos === 'number') {
|
|
||||||
partes.push(`Pacientes activos: ${estado.result.activos}`);
|
|
||||||
}
|
|
||||||
if (typeof estado.result?.antiguos === 'number') {
|
|
||||||
partes.push(`Pacientes antiguos: ${estado.result.antiguos}`);
|
|
||||||
}
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
partes.join(' - ') || 'Archivo procesado correctamente.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (estado.status === 'failed') {
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
estado.error?.message ||
|
|
||||||
'Error procesando el Excel de pacientes.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
}
|
|
||||||
this.cdr.markForCheck();
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
'Error consultando el estado del procesamiento.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
this.cdr.markForCheck();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
err?.error?.error || 'Error subiendo el Excel de pacientes.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
if (input) {
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
// Seleccionar paciente
|
// Seleccionar paciente
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@ -220,6 +148,7 @@ export class AutorizacionesComponent {
|
|||||||
this.cargandoVersiones = {};
|
this.cargandoVersiones = {};
|
||||||
this.errorAutLista = null;
|
this.errorAutLista = null;
|
||||||
this.cupsDisponibles = [];
|
this.cupsDisponibles = [];
|
||||||
|
this.cupSeleccionado = null;
|
||||||
this.errorCups = null;
|
this.errorCups = null;
|
||||||
|
|
||||||
this.cargarIps(p.interno, this.verMasIps);
|
this.cargarIps(p.interno, this.verMasIps);
|
||||||
@ -233,6 +162,7 @@ export class AutorizacionesComponent {
|
|||||||
this.errorAutorizacion = null;
|
this.errorAutorizacion = null;
|
||||||
this.errorAutLista = null;
|
this.errorAutLista = null;
|
||||||
this.cupsDisponibles = [];
|
this.cupsDisponibles = [];
|
||||||
|
this.cupSeleccionado = null;
|
||||||
this.errorCups = null;
|
this.errorCups = null;
|
||||||
this.verMasIps = false;
|
this.verMasIps = false;
|
||||||
this.departamentoInterno = '';
|
this.departamentoInterno = '';
|
||||||
@ -336,15 +266,17 @@ export class AutorizacionesComponent {
|
|||||||
const termino = String(this.formAutorizacion.cup_codigo || '').trim();
|
const termino = String(this.formAutorizacion.cup_codigo || '').trim();
|
||||||
if (!termino) {
|
if (!termino) {
|
||||||
this.cupsDisponibles = [];
|
this.cupsDisponibles = [];
|
||||||
|
this.cupSeleccionado = null;
|
||||||
this.errorCups = 'Ingresa un código o descripción para buscar.';
|
this.errorCups = 'Ingresa un código o descripción para buscar.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.buscandoCups = true;
|
this.buscandoCups = true;
|
||||||
this.errorCups = null;
|
this.errorCups = null;
|
||||||
|
this.cupSeleccionado = null;
|
||||||
|
|
||||||
this.pacienteService
|
this.pacienteService
|
||||||
.buscarCupsCubiertos(termino)
|
.buscarCups(termino)
|
||||||
.pipe(
|
.pipe(
|
||||||
finalize(() => {
|
finalize(() => {
|
||||||
this.buscandoCups = false;
|
this.buscandoCups = false;
|
||||||
@ -352,22 +284,31 @@ export class AutorizacionesComponent {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
next: (data: any[]) => {
|
next: (data: CupInfo[]) => {
|
||||||
this.cupsDisponibles = data || [];
|
this.cupsDisponibles = data || [];
|
||||||
|
const match = this.cupsDisponibles.find(
|
||||||
|
(cup) => cup.codigo === this.formAutorizacion.cup_codigo
|
||||||
|
);
|
||||||
|
this.cupSeleccionado = match || null;
|
||||||
if (this.cupsDisponibles.length === 0) {
|
if (this.cupsDisponibles.length === 0) {
|
||||||
this.errorCups = 'No se encontraron CUPS con ese criterio.';
|
this.errorCups = 'No se encontraron CUPS con ese criterio.';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
error: (err) => {
|
error: (err) => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
this.errorCups = 'Error consultando CUPS cubiertos.';
|
this.errorCups = 'Error consultando CUPS.';
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
seleccionarCup(cup: any): void {
|
seleccionarCup(cup: CupInfo): void {
|
||||||
if (!cup) return;
|
if (!cup) return;
|
||||||
this.formAutorizacion.cup_codigo = cup.codigo;
|
this.formAutorizacion.cup_codigo = cup.codigo;
|
||||||
|
this.cupSeleccionado = cup;
|
||||||
|
}
|
||||||
|
|
||||||
|
onCupInputChange(): void {
|
||||||
|
this.cupSeleccionado = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@ -407,6 +348,69 @@ export class AutorizacionesComponent {
|
|||||||
return tipo ? String(tipo) : '';
|
return tipo ? String(tipo) : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getEstadoAutorizacionLabel(estado: string | null | undefined): string {
|
||||||
|
const normalizado = String(estado || 'pendiente').toLowerCase();
|
||||||
|
if (normalizado === 'autorizado') {
|
||||||
|
return 'Autorizado';
|
||||||
|
}
|
||||||
|
if (normalizado === 'no_autorizado') {
|
||||||
|
return 'No autorizado';
|
||||||
|
}
|
||||||
|
return 'Pendiente';
|
||||||
|
}
|
||||||
|
|
||||||
|
puedeDescargarPdfAutorizacion(autorizacion: any): boolean {
|
||||||
|
if (this.isAdministrador()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const estado = String(autorizacion?.estado_autorizacion || 'pendiente').toLowerCase();
|
||||||
|
return estado === 'autorizado';
|
||||||
|
}
|
||||||
|
|
||||||
|
actualizarEstadoAutorizacion(
|
||||||
|
autorizacion: any,
|
||||||
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
||||||
|
): void {
|
||||||
|
if (!autorizacion || !this.isAdministrador()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numero = String(autorizacion.numero_autorizacion || '');
|
||||||
|
if (!numero) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.actualizandoEstado[numero]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.actualizandoEstado[numero] = true;
|
||||||
|
this.errorAutLista = null;
|
||||||
|
const estadoPrevio = autorizacion.estado_autorizacion || 'pendiente';
|
||||||
|
autorizacion.estado_autorizacion = estado;
|
||||||
|
|
||||||
|
this.pacienteService
|
||||||
|
.actualizarEstadoAutorizacion(numero, estado)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.actualizandoEstado[numero] = false;
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (resp) => {
|
||||||
|
autorizacion.estado_autorizacion =
|
||||||
|
resp?.autorizacion?.estado_autorizacion || estado;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
autorizacion.estado_autorizacion = estadoPrevio;
|
||||||
|
this.errorAutLista =
|
||||||
|
err?.error?.error || 'Error actualizando el estado de la autorizacion.';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private buildObservacionFinal(): string | undefined {
|
private buildObservacionFinal(): string | undefined {
|
||||||
const base = String(this.formAutorizacion.observacion || '').trim();
|
const base = String(this.formAutorizacion.observacion || '').trim();
|
||||||
const trasladoTexto = String(this.observacionTraslado || '').trim();
|
const trasladoTexto = String(this.observacionTraslado || '').trim();
|
||||||
@ -453,6 +457,7 @@ export class AutorizacionesComponent {
|
|||||||
tipo_autorizacion: autorizacion.tipo_autorizacion || 'consultas_externas',
|
tipo_autorizacion: autorizacion.tipo_autorizacion || 'consultas_externas',
|
||||||
tipo_servicio: autorizacion.tipo_servicio || '',
|
tipo_servicio: autorizacion.tipo_servicio || '',
|
||||||
};
|
};
|
||||||
|
this.cupSeleccionado = null;
|
||||||
|
|
||||||
this.onTipoAutorizacionChange();
|
this.onTipoAutorizacionChange();
|
||||||
this.onIpsChange();
|
this.onIpsChange();
|
||||||
@ -464,6 +469,7 @@ export class AutorizacionesComponent {
|
|||||||
cancelarEdicion(): void {
|
cancelarEdicion(): void {
|
||||||
this.autorizacionEditando = null;
|
this.autorizacionEditando = null;
|
||||||
this.observacionTraslado = '';
|
this.observacionTraslado = '';
|
||||||
|
this.cupSeleccionado = null;
|
||||||
this.formAutorizacion = {
|
this.formAutorizacion = {
|
||||||
id_ips: '',
|
id_ips: '',
|
||||||
numero_documento_autorizante: '',
|
numero_documento_autorizante: '',
|
||||||
@ -495,7 +501,6 @@ export class AutorizacionesComponent {
|
|||||||
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tipoAutorizacion = String(
|
const tipoAutorizacion = String(
|
||||||
this.formAutorizacion.tipo_autorizacion || 'consultas_externas'
|
this.formAutorizacion.tipo_autorizacion || 'consultas_externas'
|
||||||
).toLowerCase();
|
).toLowerCase();
|
||||||
@ -633,7 +638,17 @@ export class AutorizacionesComponent {
|
|||||||
// -------------------------
|
// -------------------------
|
||||||
// Descargar PDF
|
// Descargar PDF
|
||||||
// -------------------------
|
// -------------------------
|
||||||
descargarPdf(numeroAutorizacion: string, version?: number | null): void {
|
descargarPdf(autorizacion: any, version?: number | null): void {
|
||||||
|
if (!autorizacion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.puedeDescargarPdfAutorizacion(autorizacion)) {
|
||||||
|
this.errorAutLista = 'Autorizacion pendiente o no autorizada.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const numeroAutorizacion = autorizacion.numero_autorizacion;
|
||||||
this.descargandoPdf = true;
|
this.descargandoPdf = true;
|
||||||
this.errorAutLista = null;
|
this.errorAutLista = null;
|
||||||
|
|
||||||
@ -701,6 +716,10 @@ export class AutorizacionesComponent {
|
|||||||
this.router.navigate(['/autorizaciones-por-fecha']);
|
this.router.navigate(['/autorizaciones-por-fecha']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irACargarPacientes(): void {
|
||||||
|
this.router.navigate(['/cargar-pacientes']);
|
||||||
|
}
|
||||||
|
|
||||||
irADashboard(): void {
|
irADashboard(): void {
|
||||||
this.router.navigate(['/dashboard']);
|
this.router.navigate(['/dashboard']);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,108 @@
|
|||||||
|
/* ============================
|
||||||
|
Carga masiva autorizaciones
|
||||||
|
============================ */
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
<div class="page-shell">
|
||||||
|
<div class="content-container masivas-container">
|
||||||
|
<app-header
|
||||||
|
title="Carga masiva de autorizaciones"
|
||||||
|
subtitle="Sube la plantilla Excel y genera autorizaciones pendientes"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="isLoggedIn()"
|
||||||
|
[userName]="getCurrentUser()?.nombre_completo"
|
||||||
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
|
[showLogout]="isLoggedIn()"
|
||||||
|
(logout)="logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="volverDashboard()"
|
||||||
|
></app-header>
|
||||||
|
|
||||||
|
<div class="card masivas-card">
|
||||||
|
<h2>Subir plantilla</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
La columna de numero de autorizacion se ignora. Todas las autorizaciones quedan en estado pendiente.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Archivo Excel:</label>
|
||||||
|
<input
|
||||||
|
#archivoInput
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
(change)="onArchivoSelected($event)"
|
||||||
|
/>
|
||||||
|
<span class="file-name" *ngIf="archivoFile">{{ archivoFile.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row acciones">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="procesarMasivo(archivoInput)"
|
||||||
|
[disabled]="isLoading || !archivoFile"
|
||||||
|
>
|
||||||
|
{{ isLoading ? 'Procesando...' : 'Procesar autorizaciones' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status ok" *ngIf="statusMessage">{{ statusMessage }}</div>
|
||||||
|
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card resumen-card" *ngIf="resumen">
|
||||||
|
<h2>Resumen de la carga</h2>
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Total filas</span>
|
||||||
|
<span class="summary-value">{{ resumen.total || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Creadas</span>
|
||||||
|
<span class="summary-value">{{ resumen.creadas || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Omitidas</span>
|
||||||
|
<span class="summary-value">{{ resumen.omitidas || resumen.omitidos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Duplicadas</span>
|
||||||
|
<span class="summary-value">{{ resumen.duplicados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Sin paciente</span>
|
||||||
|
<span class="summary-value">{{ resumen.sin_paciente || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Sin CUPS</span>
|
||||||
|
<span class="summary-value">{{ resumen.sin_cups || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Sin IPS</span>
|
||||||
|
<span class="summary-value">{{ resumen.sin_ips || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">CUPS no cubiertos</span>
|
||||||
|
<span class="summary-value">{{ resumen.cups_no_cubiertos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">IPS sin convenio</span>
|
||||||
|
<span class="summary-value">{{ resumen.ips_sin_convenio || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="summary-note"
|
||||||
|
*ngIf="(resumen.cups_no_cubiertos || 0) > 0 || (resumen.ips_sin_convenio || 0) > 0"
|
||||||
|
>
|
||||||
|
Se detectaron CUPS no cubiertos o IPS sin convenio. Las autorizaciones quedan pendientes para revision.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-list" *ngIf="errores.length">
|
||||||
|
<h3>Errores (max 50)</h3>
|
||||||
|
<div class="error-table">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Fila</th>
|
||||||
|
<th>Error</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let e of errores">
|
||||||
|
<td>{{ e.fila || '-' }}</td>
|
||||||
|
<td>{{ e.error || 'Error en fila' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,124 @@
|
|||||||
|
import { ChangeDetectorRef, Component } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
|
import { AuthService } from '../../services/auth';
|
||||||
|
import { PacienteService } from '../../services/paciente';
|
||||||
|
import { JobsService } from '../../services/jobs';
|
||||||
|
import { JobResult, JobRowError } from '../../services/job-types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cargar-autorizaciones-masivas',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AppHeaderComponent],
|
||||||
|
templateUrl: './cargar-autorizaciones-masivas.html',
|
||||||
|
styleUrls: ['./cargar-autorizaciones-masivas.css']
|
||||||
|
})
|
||||||
|
export class CargarAutorizacionesMasivasComponent {
|
||||||
|
archivoFile: File | null = null;
|
||||||
|
isLoading = false;
|
||||||
|
statusMessage: string | null = null;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
resumen: JobResult | null = null;
|
||||||
|
errores: JobRowError[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private pacienteService: PacienteService,
|
||||||
|
private jobsService: JobsService,
|
||||||
|
private router: Router,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
volverDashboard(): void {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): any {
|
||||||
|
return this.authService.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
onArchivoSelected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
this.archivoFile = input.files?.[0] || null;
|
||||||
|
this.limpiarMensajes();
|
||||||
|
this.resumen = null;
|
||||||
|
this.errores = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
procesarMasivo(input?: HTMLInputElement): void {
|
||||||
|
this.limpiarMensajes();
|
||||||
|
this.resumen = null;
|
||||||
|
this.errores = [];
|
||||||
|
|
||||||
|
if (!this.archivoFile) {
|
||||||
|
this.errorMessage = 'Debes seleccionar el archivo Excel de autorizaciones.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.statusMessage = 'Subiendo archivo...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('archivo', this.archivoFile);
|
||||||
|
|
||||||
|
this.pacienteService.cargarAutorizacionesMasivas(formData).subscribe({
|
||||||
|
next: (job) => {
|
||||||
|
this.statusMessage = 'Archivo en cola. Procesando...';
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
|
next: (estado) => {
|
||||||
|
if (estado.status === 'completed') {
|
||||||
|
this.resumen = estado.result || null;
|
||||||
|
this.errores = Array.isArray(estado.result?.errores)
|
||||||
|
? estado.result?.errores || []
|
||||||
|
: [];
|
||||||
|
this.statusMessage =
|
||||||
|
estado.result?.mensaje || 'Carga masiva finalizada.';
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estado.status === 'failed') {
|
||||||
|
this.errorMessage =
|
||||||
|
estado.error?.message ||
|
||||||
|
'Error procesando autorizaciones masivas.';
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.errorMessage =
|
||||||
|
'Error consultando el estado de la carga masiva.';
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorMessage =
|
||||||
|
err?.error?.error || 'Error subiendo el Excel de autorizaciones.';
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private limpiarMensajes(): void {
|
||||||
|
this.statusMessage = null;
|
||||||
|
this.errorMessage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,10 +3,15 @@
|
|||||||
<app-header
|
<app-header
|
||||||
title="Cargar CUPS"
|
title="Cargar CUPS"
|
||||||
subtitle="Sube nota tecnica y tabla referencia"
|
subtitle="Sube nota tecnica y tabla referencia"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="isLoggedIn()"
|
||||||
|
[userName]="getCurrentUser()?.nombre_completo"
|
||||||
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
|
[showLogout]="isLoggedIn()"
|
||||||
|
(logout)="logout()"
|
||||||
[showBack]="true"
|
[showBack]="true"
|
||||||
backLabel="Volver"
|
backLabel="Volver"
|
||||||
(back)="volverDashboard()"
|
(back)="volverDashboard()"
|
||||||
[showLogo]="false"
|
|
||||||
></app-header>
|
></app-header>
|
||||||
|
|
||||||
<div class="card cups-card">
|
<div class="card cups-card">
|
||||||
|
|||||||
@ -38,6 +38,18 @@ export class CargarCupsComponent implements OnInit {
|
|||||||
this.router.navigate(['/dashboard']);
|
this.router.navigate(['/dashboard']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): any {
|
||||||
|
return this.authService.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
onNotaSelected(event: Event): void {
|
onNotaSelected(event: Event): void {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
this.notaFile = input.files?.[0] || null;
|
this.notaFile = input.files?.[0] || null;
|
||||||
|
|||||||
@ -0,0 +1,101 @@
|
|||||||
|
/* ============================
|
||||||
|
Carga IPS y REPS
|
||||||
|
============================ */
|
||||||
|
.ips-reps-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.upload-card h2 {
|
||||||
|
margin: 0 0 6px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 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.1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.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: 220px;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
@ -0,0 +1,147 @@
|
|||||||
|
<div class="page-shell">
|
||||||
|
<div class="content-container ips-reps-container">
|
||||||
|
<app-header
|
||||||
|
title="Cargar IPS y REPS"
|
||||||
|
subtitle="Actualiza prestadores e informacion de profesionales"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="isLoggedIn()"
|
||||||
|
[userName]="getCurrentUser()?.nombre_completo"
|
||||||
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
|
[showLogout]="isLoggedIn()"
|
||||||
|
(logout)="logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="volverDashboard()"
|
||||||
|
></app-header>
|
||||||
|
|
||||||
|
<div class="upload-grid">
|
||||||
|
<div class="card upload-card">
|
||||||
|
<h2>Cargar IPS</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
Sube el archivo ips.xlsx para actualizar convenios y datos de IPS.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Archivo Excel:</label>
|
||||||
|
<input
|
||||||
|
#ipsInput
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
(change)="onIpsSelected($event)"
|
||||||
|
/>
|
||||||
|
<span class="file-name" *ngIf="ipsFile">{{ ipsFile.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row acciones">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="procesarIps(ipsInput)"
|
||||||
|
[disabled]="isLoadingIps || !ipsFile"
|
||||||
|
>
|
||||||
|
{{ isLoadingIps ? 'Procesando...' : 'Cargar IPS' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status ok" *ngIf="statusIps">{{ statusIps }}</div>
|
||||||
|
<div class="status error" *ngIf="errorIps">{{ errorIps }}</div>
|
||||||
|
|
||||||
|
<div class="summary-grid" *ngIf="ipsResumen">
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Total filas</span>
|
||||||
|
<span class="summary-value">{{ ipsResumen.total || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Insertadas</span>
|
||||||
|
<span class="summary-value">{{ ipsResumen.insertados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Actualizadas</span>
|
||||||
|
<span class="summary-value">{{ ipsResumen.actualizados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Omitidas</span>
|
||||||
|
<span class="summary-value">{{ ipsResumen.omitidos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Desactivadas</span>
|
||||||
|
<span class="summary-value">{{ ipsResumen.desactivados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error-list" *ngIf="ipsErrores.length">
|
||||||
|
<h3>Errores (max 50)</h3>
|
||||||
|
<div class="error-table">
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Fila</th>
|
||||||
|
<th>Error</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let e of ipsErrores">
|
||||||
|
<td>{{ e.fila || '-' }}</td>
|
||||||
|
<td>{{ e.error || 'Error en fila' }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card upload-card">
|
||||||
|
<h2>Cargar REPS</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
Sube el archivo reps.xlsx para actualizar profesionales REPS.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Archivo Excel:</label>
|
||||||
|
<input
|
||||||
|
#repsInput
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
(change)="onRepsSelected($event)"
|
||||||
|
/>
|
||||||
|
<span class="file-name" *ngIf="repsFile">{{ repsFile.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row acciones">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="procesarReps(repsInput)"
|
||||||
|
[disabled]="isLoadingReps || !repsFile"
|
||||||
|
>
|
||||||
|
{{ isLoadingReps ? 'Procesando...' : 'Cargar REPS' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status ok" *ngIf="statusReps">{{ statusReps }}</div>
|
||||||
|
<div class="status error" *ngIf="errorReps">{{ errorReps }}</div>
|
||||||
|
|
||||||
|
<div class="summary-grid" *ngIf="repsResumen">
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Total filas</span>
|
||||||
|
<span class="summary-value">{{ repsResumen.total || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Insertadas</span>
|
||||||
|
<span class="summary-value">{{ repsResumen.insertados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Actualizadas</span>
|
||||||
|
<span class="summary-value">{{ repsResumen.actualizados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Omitidas</span>
|
||||||
|
<span class="summary-value">{{ repsResumen.omitidos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Desactivados</span>
|
||||||
|
<span class="summary-value">{{ repsResumen.desactivados || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,205 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
|
import { AuthService } from '../../services/auth';
|
||||||
|
import { PacienteService } from '../../services/paciente';
|
||||||
|
import { JobsService } from '../../services/jobs';
|
||||||
|
import { JobResult, JobRowError } from '../../services/job-types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cargar-ips-reps',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AppHeaderComponent],
|
||||||
|
templateUrl: './cargar-ips-reps.html',
|
||||||
|
styleUrls: ['./cargar-ips-reps.css']
|
||||||
|
})
|
||||||
|
export class CargarIpsRepsComponent implements OnInit {
|
||||||
|
ipsFile: File | null = null;
|
||||||
|
repsFile: File | null = null;
|
||||||
|
|
||||||
|
isLoadingIps = false;
|
||||||
|
isLoadingReps = false;
|
||||||
|
|
||||||
|
statusIps: string | null = null;
|
||||||
|
statusReps: string | null = null;
|
||||||
|
errorIps: string | null = null;
|
||||||
|
errorReps: string | null = null;
|
||||||
|
|
||||||
|
ipsResumen: JobResult | null = null;
|
||||||
|
repsResumen: JobResult | null = null;
|
||||||
|
ipsErrores: JobRowError[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private pacienteService: PacienteService,
|
||||||
|
private jobsService: JobsService,
|
||||||
|
private router: Router,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.authService.isAdministrador()) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volverDashboard(): void {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): any {
|
||||||
|
return this.authService.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
onIpsSelected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
this.ipsFile = input.files?.[0] || null;
|
||||||
|
this.limpiarIps();
|
||||||
|
this.ipsResumen = null;
|
||||||
|
this.ipsErrores = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
onRepsSelected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
this.repsFile = input.files?.[0] || null;
|
||||||
|
this.limpiarReps();
|
||||||
|
this.repsResumen = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
procesarIps(input?: HTMLInputElement): void {
|
||||||
|
this.limpiarIps();
|
||||||
|
this.ipsResumen = null;
|
||||||
|
this.ipsErrores = [];
|
||||||
|
|
||||||
|
if (!this.ipsFile) {
|
||||||
|
this.errorIps = 'Debes seleccionar el archivo Excel de IPS.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingIps = true;
|
||||||
|
this.statusIps = 'Subiendo archivo...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('archivo', this.ipsFile);
|
||||||
|
|
||||||
|
this.pacienteService.cargarIps(formData).subscribe({
|
||||||
|
next: (job) => {
|
||||||
|
this.statusIps = 'Archivo en cola. Procesando...';
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
|
next: (estado) => {
|
||||||
|
if (estado.status === 'completed') {
|
||||||
|
this.ipsResumen = estado.result || null;
|
||||||
|
this.ipsErrores = Array.isArray(estado.result?.errores)
|
||||||
|
? estado.result?.errores || []
|
||||||
|
: [];
|
||||||
|
this.statusIps = estado.result?.mensaje || 'IPS cargadas correctamente.';
|
||||||
|
this.isLoadingIps = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estado.status === 'failed') {
|
||||||
|
this.errorIps =
|
||||||
|
estado.error?.message || 'Error procesando el Excel de IPS.';
|
||||||
|
this.isLoadingIps = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.errorIps =
|
||||||
|
'Error consultando el estado de la carga de IPS.';
|
||||||
|
this.isLoadingIps = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorIps =
|
||||||
|
err?.error?.error || 'Error subiendo el Excel de IPS.';
|
||||||
|
this.isLoadingIps = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
procesarReps(input?: HTMLInputElement): void {
|
||||||
|
this.limpiarReps();
|
||||||
|
this.repsResumen = null;
|
||||||
|
|
||||||
|
if (!this.repsFile) {
|
||||||
|
this.errorReps = 'Debes seleccionar el archivo Excel de REPS.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingReps = true;
|
||||||
|
this.statusReps = 'Subiendo archivo...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('archivo', this.repsFile);
|
||||||
|
|
||||||
|
this.pacienteService.cargarReps(formData).subscribe({
|
||||||
|
next: (job) => {
|
||||||
|
this.statusReps = 'Archivo en cola. Procesando...';
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
|
next: (estado) => {
|
||||||
|
if (estado.status === 'completed') {
|
||||||
|
this.repsResumen = estado.result || null;
|
||||||
|
this.statusReps = estado.result?.mensaje || 'REPS cargados correctamente.';
|
||||||
|
this.isLoadingReps = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estado.status === 'failed') {
|
||||||
|
this.errorReps =
|
||||||
|
estado.error?.message || 'Error procesando el Excel de REPS.';
|
||||||
|
this.isLoadingReps = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.errorReps =
|
||||||
|
'Error consultando el estado de la carga de REPS.';
|
||||||
|
this.isLoadingReps = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorReps =
|
||||||
|
err?.error?.error || 'Error subiendo el Excel de REPS.';
|
||||||
|
this.isLoadingReps = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private limpiarIps(): void {
|
||||||
|
this.statusIps = null;
|
||||||
|
this.errorIps = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private limpiarReps(): void {
|
||||||
|
this.statusReps = null;
|
||||||
|
this.errorReps = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,83 @@
|
|||||||
|
/* ============================
|
||||||
|
Cargar pacientes
|
||||||
|
============================ */
|
||||||
|
.pacientes-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pacientes-card {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pacientes-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resumen-card h2 {
|
||||||
|
margin: 0 0 12px;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 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);
|
||||||
|
}
|
||||||
@ -0,0 +1,62 @@
|
|||||||
|
<div class="page-shell">
|
||||||
|
<div class="content-container pacientes-container">
|
||||||
|
<app-header
|
||||||
|
title="Cargar pacientes"
|
||||||
|
subtitle="Sube el Excel de PPL para actualizar pacientes e ingresos"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="isLoggedIn()"
|
||||||
|
[userName]="getCurrentUser()?.nombre_completo"
|
||||||
|
[userRole]="getCurrentUser()?.nombre_rol"
|
||||||
|
[showLogout]="isLoggedIn()"
|
||||||
|
(logout)="logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="volverDashboard()"
|
||||||
|
></app-header>
|
||||||
|
|
||||||
|
<div class="card pacientes-card">
|
||||||
|
<h2>Subir archivo</h2>
|
||||||
|
<p class="subtitle">
|
||||||
|
El sistema procesa paciente, ingreso y establecimiento. Usa la plantilla oficial de pacientes.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="form-row">
|
||||||
|
<label>Archivo Excel:</label>
|
||||||
|
<input
|
||||||
|
#archivoInput
|
||||||
|
type="file"
|
||||||
|
accept=".xlsx,.xls"
|
||||||
|
(change)="onArchivoSelected($event)"
|
||||||
|
/>
|
||||||
|
<span class="file-name" *ngIf="archivoFile">{{ archivoFile.name }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-row acciones">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary"
|
||||||
|
(click)="procesarPacientes(archivoInput)"
|
||||||
|
[disabled]="isLoading || !archivoFile"
|
||||||
|
>
|
||||||
|
{{ isLoading ? 'Procesando...' : 'Procesar pacientes' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status ok" *ngIf="statusMessage">{{ statusMessage }}</div>
|
||||||
|
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card resumen-card" *ngIf="resumen">
|
||||||
|
<h2>Resumen de la carga</h2>
|
||||||
|
<div class="summary-grid">
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Pacientes activos</span>
|
||||||
|
<span class="summary-value">{{ resumen.activos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="summary-item">
|
||||||
|
<span class="summary-label">Pacientes antiguos</span>
|
||||||
|
<span class="summary-value">{{ resumen.antiguos || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,133 @@
|
|||||||
|
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
|
import { AuthService } from '../../services/auth';
|
||||||
|
import { PacienteService } from '../../services/paciente';
|
||||||
|
import { JobsService } from '../../services/jobs';
|
||||||
|
import { JobResult } from '../../services/job-types';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-cargar-pacientes',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, AppHeaderComponent],
|
||||||
|
templateUrl: './cargar-pacientes.html',
|
||||||
|
styleUrls: ['./cargar-pacientes.css']
|
||||||
|
})
|
||||||
|
export class CargarPacientesComponent implements OnInit {
|
||||||
|
archivoFile: File | null = null;
|
||||||
|
isLoading = false;
|
||||||
|
statusMessage: string | null = null;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
resumen: JobResult | null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private authService: AuthService,
|
||||||
|
private pacienteService: PacienteService,
|
||||||
|
private jobsService: JobsService,
|
||||||
|
private router: Router,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.authService.isAdministrador()) {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
volverDashboard(): void {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout(): void {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggedIn(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentUser(): any {
|
||||||
|
return this.authService.getCurrentUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
onArchivoSelected(event: Event): void {
|
||||||
|
const input = event.target as HTMLInputElement;
|
||||||
|
this.archivoFile = input.files?.[0] || null;
|
||||||
|
this.limpiarMensajes();
|
||||||
|
this.resumen = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
procesarPacientes(input?: HTMLInputElement): void {
|
||||||
|
this.limpiarMensajes();
|
||||||
|
this.resumen = null;
|
||||||
|
|
||||||
|
if (!this.archivoFile) {
|
||||||
|
this.errorMessage = 'Debes seleccionar el archivo Excel de pacientes.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.statusMessage = 'Subiendo archivo...';
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('archivo', this.archivoFile);
|
||||||
|
|
||||||
|
this.pacienteService.cargarExcelPacientes(formData).subscribe({
|
||||||
|
next: (job) => {
|
||||||
|
this.statusMessage = 'Archivo en cola. Procesando...';
|
||||||
|
if (input) {
|
||||||
|
input.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.jobsService.pollJob(job.id).subscribe({
|
||||||
|
next: (estado) => {
|
||||||
|
if (estado.status === 'completed') {
|
||||||
|
this.resumen = estado.result || null;
|
||||||
|
const partes: string[] = [];
|
||||||
|
if (estado.result?.mensaje) {
|
||||||
|
partes.push(estado.result.mensaje);
|
||||||
|
}
|
||||||
|
if (typeof estado.result?.activos === 'number') {
|
||||||
|
partes.push(`Pacientes activos: ${estado.result.activos}`);
|
||||||
|
}
|
||||||
|
if (typeof estado.result?.antiguos === 'number') {
|
||||||
|
partes.push(`Pacientes antiguos: ${estado.result.antiguos}`);
|
||||||
|
}
|
||||||
|
this.statusMessage =
|
||||||
|
partes.join(' - ') || 'Pacientes cargados correctamente.';
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estado.status === 'failed') {
|
||||||
|
this.errorMessage =
|
||||||
|
estado.error?.message || 'Error procesando el Excel de pacientes.';
|
||||||
|
this.isLoading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (error) => {
|
||||||
|
console.error(error);
|
||||||
|
this.errorMessage =
|
||||||
|
'Error consultando el estado de la carga de pacientes.';
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorMessage =
|
||||||
|
err?.error?.error || 'Error subiendo el Excel de pacientes.';
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private limpiarMensajes(): void {
|
||||||
|
this.statusMessage = null;
|
||||||
|
this.errorMessage = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -38,15 +38,16 @@
|
|||||||
padding: 16px 20px;
|
padding: 16px 20px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin: 20px 0 24px;
|
margin: 20px 0 24px;
|
||||||
background: white;
|
background: var(--color-card);
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
animation: slideIn 0.3s ease;
|
animation: slideIn 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-error {
|
.alert-error {
|
||||||
border-left: 4px solid #dc2626;
|
border-left: 4px solid var(--color-error);
|
||||||
background: #fef2f2;
|
background: var(--color-permission-no-bg);
|
||||||
color: #dc2626;
|
color: var(--color-error);
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-icon {
|
.alert-icon {
|
||||||
@ -94,20 +95,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.action-card {
|
.action-card {
|
||||||
background: white;
|
background: var(--color-card);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
border: 2px solid transparent;
|
color: var(--color-text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-card:hover {
|
.action-card:hover {
|
||||||
transform: translateY(-4px);
|
transform: translateY(-4px);
|
||||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
box-shadow: var(--shadow-float);
|
||||||
border-color: #1976d2;
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-icon {
|
.action-icon {
|
||||||
@ -117,8 +119,8 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
color: #1976d2;
|
color: var(--color-primary);
|
||||||
background: #e3f2fd;
|
background: var(--color-primary-soft);
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,14 +132,14 @@
|
|||||||
.action-card h4,
|
.action-card h4,
|
||||||
.action-card h3 {
|
.action-card h3 {
|
||||||
margin: 0 0 8px 0;
|
margin: 0 0 8px 0;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
font-size: 1.05rem;
|
font-size: 1.05rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-card p {
|
.action-card p {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
line-height: 1.4;
|
line-height: 1.4;
|
||||||
}
|
}
|
||||||
@ -146,7 +148,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
top: 12px;
|
top: 12px;
|
||||||
right: 12px;
|
right: 12px;
|
||||||
background: #1976d2;
|
background: var(--color-primary);
|
||||||
color: white;
|
color: white;
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@ -171,12 +173,12 @@
|
|||||||
|
|
||||||
.role-label {
|
.role-label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
min-width: 140px;
|
min-width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.role-value {
|
.role-value {
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,36 +188,90 @@
|
|||||||
gap: 8px;
|
gap: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.sedes-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sedes-meta {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sedes-count {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sedes-toggle {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--color-primary);
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sedes-toggle:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sedes-empty {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
.sede-badge {
|
.sede-badge {
|
||||||
background: #e3f2fd;
|
background: var(--color-primary-soft);
|
||||||
color: #1976d2;
|
color: var(--color-primary);
|
||||||
padding: 4px 12px;
|
padding: 4px 12px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
border: 1px solid #1976d2;
|
border: 1px solid var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Permissions Summary */
|
/* Permissions Summary */
|
||||||
|
.permissions-summary {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-subtitle {
|
||||||
|
margin: -8px 0 16px;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
.permissions-grid {
|
.permissions-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||||
gap: 12px;
|
gap: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission-item {
|
.permission-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: flex-start;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 12px;
|
padding: 14px 16px;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
background: #f9fafb;
|
background: var(--color-surface);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission-item.has-permission {
|
.permission-item.has-permission {
|
||||||
background: #f0f9f0;
|
background: var(--color-permission-yes-bg);
|
||||||
border: 1px solid #dcfce7;
|
border: 1px solid var(--color-permission-yes-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item.no-permission {
|
||||||
|
background: var(--color-permission-no-bg);
|
||||||
|
border: 1px solid var(--color-permission-no-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission-icon {
|
.permission-icon {
|
||||||
@ -226,11 +282,31 @@
|
|||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
|
color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.permission-text {
|
.permission-item.has-permission .permission-icon {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-item.no-permission .permission-icon {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-title {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #222222;
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-meta {
|
||||||
|
font-size: 0.82rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.excel-status {
|
.excel-status {
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
<app-header
|
<app-header
|
||||||
title="SALUD UT"
|
title="SALUD UT"
|
||||||
subtitle="Módulo de autorizaciones médicas"
|
subtitle="Módulo de autorizaciones médicas"
|
||||||
|
badgeText="SALUD UT"
|
||||||
[showUserInfo]="true"
|
[showUserInfo]="true"
|
||||||
[userName]="getNombreUsuario()"
|
[userName]="getNombreUsuario()"
|
||||||
[userRole]="getNombreRolFormateado()"
|
[userRole]="getNombreRolFormateado()"
|
||||||
@ -50,7 +51,7 @@
|
|||||||
<div
|
<div
|
||||||
class="action-card"
|
class="action-card"
|
||||||
*ngIf="puedeCargarPacientes()"
|
*ngIf="puedeCargarPacientes()"
|
||||||
(click)="abrirCargadorPacientes(inputExcelPacientes)"
|
(click)="irACargarPacientes()"
|
||||||
>
|
>
|
||||||
<span class="action-icon" aria-hidden="true">
|
<span class="action-icon" aria-hidden="true">
|
||||||
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||||
@ -67,9 +68,7 @@
|
|||||||
</span>
|
</span>
|
||||||
<h4>Cargar pacientes</h4>
|
<h4>Cargar pacientes</h4>
|
||||||
<p>
|
<p>
|
||||||
{{ cargandoExcel
|
Subir archivo Excel con datos de pacientes
|
||||||
? 'Cargando archivo de pacientes...'
|
|
||||||
: 'Subir archivo Excel con datos de pacientes' }}
|
|
||||||
</p>
|
</p>
|
||||||
<div class="admin-badge">Solo admin</div>
|
<div class="admin-badge">Solo admin</div>
|
||||||
</div>
|
</div>
|
||||||
@ -100,11 +99,67 @@
|
|||||||
<div class="admin-badge">Solo admin</div>
|
<div class="admin-badge">Solo admin</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Ver Autorizaciones por Fecha (solo administradores) -->
|
<!-- Carga masiva de autorizaciones -->
|
||||||
|
<div
|
||||||
|
class="action-card"
|
||||||
|
(click)="irACargarAutorizacionesMasivas()"
|
||||||
|
>
|
||||||
|
<span class="action-icon" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||||
|
<path
|
||||||
|
d="M4 5h16v10H4z"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M8 19h8"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
></path>
|
||||||
|
<path
|
||||||
|
d="M9 9h6M9 12h6"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<h4>Carga masiva autorizaciones</h4>
|
||||||
|
<p>Subir plantilla Excel para crear autorizaciones pendientes</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Cargar IPS y REPS (solo administradores) -->
|
||||||
|
<div
|
||||||
|
class="action-card"
|
||||||
|
*ngIf="puedeCargarIpsReps()"
|
||||||
|
(click)="irACargarIpsReps()"
|
||||||
|
>
|
||||||
|
<span class="action-icon" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||||
|
<path
|
||||||
|
d="M12 3v12m0 0l-4-4m4 4l4-4"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
></path>
|
||||||
|
<rect x="4" y="17" width="16" height="4" rx="1" fill="none" stroke="currentColor" stroke-width="2"></rect>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<h4>Cargar IPS y REPS</h4>
|
||||||
|
<p>Subir Excel de IPS y profesionales REPS</p>
|
||||||
|
<div class="admin-badge">Solo admin</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ver Autorizaciones por Fecha -->
|
||||||
<div
|
<div
|
||||||
class="action-card"
|
class="action-card"
|
||||||
(click)="irAVerAutorizacionesPorFecha()"
|
(click)="irAVerAutorizacionesPorFecha()"
|
||||||
*ngIf="puedeVerTodasAutorizaciones()"
|
|
||||||
>
|
>
|
||||||
<span class="action-icon" aria-hidden="true">
|
<span class="action-icon" aria-hidden="true">
|
||||||
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||||
@ -116,6 +171,28 @@
|
|||||||
</span>
|
</span>
|
||||||
<h4>Autorizaciones por fecha</h4>
|
<h4>Autorizaciones por fecha</h4>
|
||||||
<p>Consultar y descargar autorizaciones por rango de fechas</p>
|
<p>Consultar y descargar autorizaciones por rango de fechas</p>
|
||||||
|
<div class="admin-badge" *ngIf="puedeVerTodasAutorizaciones()">Solo admin</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Estadisticas de autorizaciones (solo administradores) -->
|
||||||
|
<div
|
||||||
|
class="action-card"
|
||||||
|
*ngIf="puedeVerEstadisticas()"
|
||||||
|
(click)="irAEstadisticasAutorizaciones()"
|
||||||
|
>
|
||||||
|
<span class="action-icon" aria-hidden="true">
|
||||||
|
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||||
|
<path
|
||||||
|
d="M4 20V10M10 20V4M16 20v-7M22 20H2"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
></path>
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
<h4>Estadisticas</h4>
|
||||||
|
<p>Resumen mensual con estados y volumen diario</p>
|
||||||
<div class="admin-badge">Solo admin</div>
|
<div class="admin-badge">Solo admin</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -143,20 +220,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Input de archivo real (oculto) -->
|
|
||||||
<input
|
|
||||||
#inputExcelPacientes
|
|
||||||
type="file"
|
|
||||||
accept=".xlsx,.xls"
|
|
||||||
hidden
|
|
||||||
(click)="$event.stopPropagation()"
|
|
||||||
(change)="onExcelSelected($event)"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- Mensaje de estado de la carga -->
|
|
||||||
<p class="excel-status" *ngIf="estadoCargaExcel">
|
|
||||||
{{ estadoCargaExcel }}
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- User Role Information -->
|
<!-- User Role Information -->
|
||||||
@ -171,21 +234,36 @@
|
|||||||
<!-- Sedes asignadas (solo para administrativos) -->
|
<!-- Sedes asignadas (solo para administrativos) -->
|
||||||
<div
|
<div
|
||||||
class="role-item"
|
class="role-item"
|
||||||
*ngIf="
|
*ngIf="currentUser.nombre_rol === 'administrativo_sede'"
|
||||||
currentUser.nombre_rol === 'administrativo_sede' &&
|
|
||||||
getSedesUsuario().length > 0
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<span class="role-label">Sedes asignadas:</span>
|
<span class="role-label">Sedes asignadas:</span>
|
||||||
<div class="sedes-list">
|
<div class="sedes-info">
|
||||||
|
<div class="sedes-meta" *ngIf="getSedesUsuario().length > 0">
|
||||||
|
<span class="sedes-count">
|
||||||
|
{{ getSedesUsuario().length }} sedes
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
class="sedes-toggle"
|
||||||
|
*ngIf="getSedesUsuario().length > 8"
|
||||||
|
(click)="toggleSedesAsignadas()"
|
||||||
|
type="button"
|
||||||
|
>
|
||||||
|
{{ getTextoToggleSedes() }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="sedes-list" *ngIf="getSedesUsuario().length > 0; else sinSedes">
|
||||||
<span
|
<span
|
||||||
class="sede-badge"
|
class="sede-badge"
|
||||||
*ngFor="let sede of getSedesUsuario()"
|
*ngFor="let sede of getSedesMostradas()"
|
||||||
[title]="sede.nombre_establecimiento"
|
[title]="sede.nombre_establecimiento"
|
||||||
>
|
>
|
||||||
{{ sede.nombre_establecimiento }}
|
{{ sede.nombre_establecimiento }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
<ng-template #sinSedes>
|
||||||
|
<span class="sedes-empty">Sin sedes asignadas</span>
|
||||||
|
</ng-template>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -193,10 +271,14 @@
|
|||||||
<!-- Permissions Summary -->
|
<!-- Permissions Summary -->
|
||||||
<section class="permissions-summary card">
|
<section class="permissions-summary card">
|
||||||
<h3 class="section-title">Permisos disponibles</h3>
|
<h3 class="section-title">Permisos disponibles</h3>
|
||||||
|
<p class="section-subtitle">
|
||||||
|
Resumen de lo que puedes hacer con tu cuenta.
|
||||||
|
</p>
|
||||||
<div class="permissions-grid">
|
<div class="permissions-grid">
|
||||||
<div
|
<div
|
||||||
class="permission-item"
|
class="permission-item"
|
||||||
[class.has-permission]="puedeGenerarAutorizaciones()"
|
[class.has-permission]="puedeGenerarAutorizaciones()"
|
||||||
|
[class.no-permission]="!puedeGenerarAutorizaciones()"
|
||||||
>
|
>
|
||||||
<span class="permission-icon" *ngIf="puedeGenerarAutorizaciones(); else noPermGen">
|
<span class="permission-icon" *ngIf="puedeGenerarAutorizaciones(); else noPermGen">
|
||||||
✓
|
✓
|
||||||
@ -204,25 +286,56 @@
|
|||||||
<ng-template #noPermGen>
|
<ng-template #noPermGen>
|
||||||
<span class="permission-icon">×</span>
|
<span class="permission-icon">×</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<span class="permission-text">Generar autorizaciones</span>
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Generar autorizaciones</span>
|
||||||
|
<span class="permission-meta">Crear y editar solicitudes</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="permission-item"
|
class="permission-item"
|
||||||
[class.has-permission]="puedeCargarPacientes()"
|
[class.has-permission]="puedeCargarAutorizacionesMasivas()"
|
||||||
|
[class.no-permission]="!puedeCargarAutorizacionesMasivas()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="permission-icon"
|
||||||
|
*ngIf="puedeCargarAutorizacionesMasivas(); else noPermMasivas"
|
||||||
>
|
>
|
||||||
<span class="permission-icon" *ngIf="puedeCargarPacientes(); else noPermExcel">
|
|
||||||
✓
|
✓
|
||||||
</span>
|
</span>
|
||||||
<ng-template #noPermExcel>
|
<ng-template #noPermMasivas>
|
||||||
<span class="permission-icon">×</span>
|
<span class="permission-icon">×</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<span class="permission-text">Cargar pacientes (Excel)</span>
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Cargar autorizaciones masivas</span>
|
||||||
|
<span class="permission-meta">Subir archivo con solicitudes</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeVerAutorizacionesPorFecha()"
|
||||||
|
[class.no-permission]="!puedeVerAutorizacionesPorFecha()"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="permission-icon"
|
||||||
|
*ngIf="puedeVerAutorizacionesPorFecha(); else noPermFechas"
|
||||||
|
>
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermFechas>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Ver autorizaciones por fecha</span>
|
||||||
|
<span class="permission-meta">Consultar por rango</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="permission-item"
|
class="permission-item"
|
||||||
[class.has-permission]="puedeDescargarPdfs()"
|
[class.has-permission]="puedeDescargarPdfs()"
|
||||||
|
[class.no-permission]="!puedeDescargarPdfs()"
|
||||||
>
|
>
|
||||||
<span class="permission-icon" *ngIf="puedeDescargarPdfs(); else noPermPdf">
|
<span class="permission-icon" *ngIf="puedeDescargarPdfs(); else noPermPdf">
|
||||||
✓
|
✓
|
||||||
@ -230,12 +343,16 @@
|
|||||||
<ng-template #noPermPdf>
|
<ng-template #noPermPdf>
|
||||||
<span class="permission-icon">×</span>
|
<span class="permission-icon">×</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<span class="permission-text">Descargar PDFs</span>
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Descargar PDFs</span>
|
||||||
|
<span class="permission-meta">Generar y descargar documentos</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="permission-item"
|
class="permission-item"
|
||||||
[class.has-permission]="puedeVerTodasAutorizaciones()"
|
[class.has-permission]="puedeVerTodasAutorizaciones()"
|
||||||
|
[class.no-permission]="!puedeVerTodasAutorizaciones()"
|
||||||
>
|
>
|
||||||
<span class="permission-icon" *ngIf="puedeVerTodasAutorizaciones(); else noPermAll">
|
<span class="permission-icon" *ngIf="puedeVerTodasAutorizaciones(); else noPermAll">
|
||||||
✓
|
✓
|
||||||
@ -243,7 +360,95 @@
|
|||||||
<ng-template #noPermAll>
|
<ng-template #noPermAll>
|
||||||
<span class="permission-icon">×</span>
|
<span class="permission-icon">×</span>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
<span class="permission-text">Ver todas las autorizaciones</span>
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Ver todas las autorizaciones</span>
|
||||||
|
<span class="permission-meta">Incluye pendientes y no autorizadas</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeCargarPacientes()"
|
||||||
|
[class.no-permission]="!puedeCargarPacientes()"
|
||||||
|
>
|
||||||
|
<span class="permission-icon" *ngIf="puedeCargarPacientes(); else noPermPacientes">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermPacientes>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Cargar pacientes (Excel)</span>
|
||||||
|
<span class="permission-meta">Importar datos masivos</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeCargarCups()"
|
||||||
|
[class.no-permission]="!puedeCargarCups()"
|
||||||
|
>
|
||||||
|
<span class="permission-icon" *ngIf="puedeCargarCups(); else noPermCups">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermCups>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Cargar CUPS</span>
|
||||||
|
<span class="permission-meta">Actualizar procedimientos cubiertos</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeCargarIpsReps()"
|
||||||
|
[class.no-permission]="!puedeCargarIpsReps()"
|
||||||
|
>
|
||||||
|
<span class="permission-icon" *ngIf="puedeCargarIpsReps(); else noPermIpsReps">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermIpsReps>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Cargar IPS y REPS</span>
|
||||||
|
<span class="permission-meta">Actualizar convenios y profesionales</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeVerEstadisticas()"
|
||||||
|
[class.no-permission]="!puedeVerEstadisticas()"
|
||||||
|
>
|
||||||
|
<span class="permission-icon" *ngIf="puedeVerEstadisticas(); else noPermStats">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermStats>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Ver estadisticas</span>
|
||||||
|
<span class="permission-meta">Resumen por dia, semana y mes</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="permission-item"
|
||||||
|
[class.has-permission]="puedeGestionarUsuarios()"
|
||||||
|
[class.no-permission]="!puedeGestionarUsuarios()"
|
||||||
|
>
|
||||||
|
<span class="permission-icon" *ngIf="puedeGestionarUsuarios(); else noPermUsers">
|
||||||
|
✓
|
||||||
|
</span>
|
||||||
|
<ng-template #noPermUsers>
|
||||||
|
<span class="permission-icon">×</span>
|
||||||
|
</ng-template>
|
||||||
|
<div class="permission-body">
|
||||||
|
<span class="permission-title">Gestionar usuarios</span>
|
||||||
|
<span class="permission-meta">Crear y editar accesos</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -1,16 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
Component,
|
Component,
|
||||||
OnInit,
|
OnInit,
|
||||||
OnDestroy,
|
OnDestroy
|
||||||
ChangeDetectorRef
|
|
||||||
} from '@angular/core';
|
} from '@angular/core';
|
||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { Router, ActivatedRoute } from '@angular/router';
|
import { Router, ActivatedRoute } from '@angular/router';
|
||||||
import { Subscription } from 'rxjs';
|
import { Subscription } from 'rxjs';
|
||||||
import { AuthService } from '../../services/auth';
|
import { AuthService } from '../../services/auth';
|
||||||
import { PacienteService } from '../../services/paciente';
|
|
||||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
import { JobsService } from '../../services/jobs';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-dashboard',
|
selector: 'app-dashboard',
|
||||||
@ -23,18 +20,12 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
currentUser: any = null;
|
currentUser: any = null;
|
||||||
errorMessage: string | null = null;
|
errorMessage: string | null = null;
|
||||||
private subscriptions: Subscription[] = [];
|
private subscriptions: Subscription[] = [];
|
||||||
|
mostrarTodasSedes = false;
|
||||||
// ---- Carga de Excel ----
|
|
||||||
cargandoExcel = false;
|
|
||||||
estadoCargaExcel: string | null = null;
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private authService: AuthService,
|
private authService: AuthService,
|
||||||
private router: Router,
|
private router: Router,
|
||||||
private route: ActivatedRoute,
|
private route: ActivatedRoute
|
||||||
private pacienteService: PacienteService,
|
|
||||||
private jobsService: JobsService,
|
|
||||||
private cdr: ChangeDetectorRef
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
@ -98,10 +89,26 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
this.router.navigate(['/autorizaciones-por-fecha']);
|
this.router.navigate(['/autorizaciones-por-fecha']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irAEstadisticasAutorizaciones(): void {
|
||||||
|
this.router.navigate(['/estadisticas-autorizaciones']);
|
||||||
|
}
|
||||||
|
|
||||||
irACargarCups(): void {
|
irACargarCups(): void {
|
||||||
this.router.navigate(['/cargar-cups']);
|
this.router.navigate(['/cargar-cups']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
irACargarPacientes(): void {
|
||||||
|
this.router.navigate(['/cargar-pacientes']);
|
||||||
|
}
|
||||||
|
|
||||||
|
irACargarAutorizacionesMasivas(): void {
|
||||||
|
this.router.navigate(['/cargar-autorizaciones-masivas']);
|
||||||
|
}
|
||||||
|
|
||||||
|
irACargarIpsReps(): void {
|
||||||
|
this.router.navigate(['/cargar-ips-reps']);
|
||||||
|
}
|
||||||
|
|
||||||
irAUsuarios(): void {
|
irAUsuarios(): void {
|
||||||
this.router.navigate(['/usuarios']);
|
this.router.navigate(['/usuarios']);
|
||||||
}
|
}
|
||||||
@ -149,6 +156,10 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
return this.authService.isAdministrador();
|
return this.authService.isAdministrador();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
puedeCargarIpsReps(): boolean {
|
||||||
|
return this.authService.isAdministrador();
|
||||||
|
}
|
||||||
|
|
||||||
puedeDescargarPdfs(): boolean {
|
puedeDescargarPdfs(): boolean {
|
||||||
return this.authService.puedeDescargarPdfs();
|
return this.authService.puedeDescargarPdfs();
|
||||||
}
|
}
|
||||||
@ -157,14 +168,42 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
return this.authService.puedeVerTodasAutorizaciones();
|
return this.authService.puedeVerTodasAutorizaciones();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
puedeVerEstadisticas(): boolean {
|
||||||
|
return this.authService.isAdministrador();
|
||||||
|
}
|
||||||
|
|
||||||
puedeGenerarAutorizaciones(): boolean {
|
puedeGenerarAutorizaciones(): boolean {
|
||||||
return this.authService.puedeGenerarAutorizaciones();
|
return this.authService.puedeGenerarAutorizaciones();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
puedeCargarAutorizacionesMasivas(): boolean {
|
||||||
|
return this.authService.puedeGenerarAutorizaciones();
|
||||||
|
}
|
||||||
|
|
||||||
|
puedeVerAutorizacionesPorFecha(): boolean {
|
||||||
|
return this.authService.isLoggedIn();
|
||||||
|
}
|
||||||
|
|
||||||
getSedesUsuario(): any[] {
|
getSedesUsuario(): any[] {
|
||||||
return this.currentUser?.sedes || [];
|
return this.currentUser?.sedes || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getSedesMostradas(): any[] {
|
||||||
|
const sedes = this.getSedesUsuario();
|
||||||
|
if (this.mostrarTodasSedes || sedes.length <= 8) {
|
||||||
|
return sedes;
|
||||||
|
}
|
||||||
|
return sedes.slice(0, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleSedesAsignadas(): void {
|
||||||
|
this.mostrarTodasSedes = !this.mostrarTodasSedes;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTextoToggleSedes(): string {
|
||||||
|
return this.mostrarTodasSedes ? 'Ocultar' : 'Ver todas';
|
||||||
|
}
|
||||||
|
|
||||||
cerrarMensajeError(): void {
|
cerrarMensajeError(): void {
|
||||||
this.errorMessage = null;
|
this.errorMessage = null;
|
||||||
}
|
}
|
||||||
@ -178,83 +217,4 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
|||||||
day: 'numeric'
|
day: 'numeric'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// =========================
|
|
||||||
// Cargar pacientes (Excel)
|
|
||||||
// =========================
|
|
||||||
abrirCargadorPacientes(input: HTMLInputElement): void {
|
|
||||||
if (!this.puedeCargarPacientes() || this.cargandoExcel) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.estadoCargaExcel = null;
|
|
||||||
input.click();
|
|
||||||
}
|
|
||||||
|
|
||||||
onExcelSelected(event: Event): void {
|
|
||||||
const input = event.target as HTMLInputElement;
|
|
||||||
const file = input.files?.[0];
|
|
||||||
|
|
||||||
if (!file) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cargandoExcel = true;
|
|
||||||
this.estadoCargaExcel = 'Subiendo archivo...';
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('archivo', file); // mismo nombre que en server.js
|
|
||||||
|
|
||||||
this.pacienteService.cargarExcelPacientes(formData).subscribe({
|
|
||||||
next: (job) => {
|
|
||||||
this.estadoCargaExcel = 'Archivo en cola. Procesando...';
|
|
||||||
if (input) {
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
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?.activos === 'number') {
|
|
||||||
partes.push(`Pacientes activos: ${estado.result.activos}`);
|
|
||||||
}
|
|
||||||
if (typeof estado.result?.antiguos === 'number') {
|
|
||||||
partes.push(`Pacientes antiguos: ${estado.result.antiguos}`);
|
|
||||||
}
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
partes.join(' ? ') || 'Archivo procesado correctamente.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (estado.status === 'failed') {
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
estado.error?.message ||
|
|
||||||
'Error procesando el Excel de pacientes.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
}
|
|
||||||
this.cdr.detectChanges();
|
|
||||||
},
|
|
||||||
error: (error) => {
|
|
||||||
console.error(error);
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
'Error consultando el estado del procesamiento.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
this.cdr.detectChanges();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
console.error(err);
|
|
||||||
this.estadoCargaExcel =
|
|
||||||
err?.error?.error || 'Error subiendo el Excel de pacientes.';
|
|
||||||
this.cargandoExcel = false;
|
|
||||||
if (input) {
|
|
||||||
input.value = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,317 @@
|
|||||||
|
.controles-card .form-row {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.controles-card label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controles-card select,
|
||||||
|
.controles-card input[type="date"],
|
||||||
|
.controles-card input[type="month"],
|
||||||
|
.controles-card input[type="number"] {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.controles-card select:focus,
|
||||||
|
.controles-card input[type="date"]:focus,
|
||||||
|
.controles-card input[type="month"]:focus,
|
||||||
|
.controles-card input[type="number"]:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #1976d2;
|
||||||
|
box-shadow: 0 0 0 3px rgba(25, 118, 210, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stats-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||||||
|
gap: 16px;
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card {
|
||||||
|
background: var(--color-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-card.total {
|
||||||
|
background: var(--color-primary-soft);
|
||||||
|
}
|
||||||
|
|
||||||
|
.stat-value {
|
||||||
|
margin: 10px 0 0;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-card {
|
||||||
|
margin-top: 20px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: linear-gradient(180deg, var(--color-surface) 0%, var(--color-surface-alt) 100%);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-card::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at 20% 0%, rgba(25, 118, 210, 0.12), transparent 55%);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-card > * {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-range {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
background: var(--color-surface-muted);
|
||||||
|
padding: 6px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
gap: 6px;
|
||||||
|
height: 230px;
|
||||||
|
overflow-x: auto;
|
||||||
|
padding: 18px 14px 10px;
|
||||||
|
border-radius: 14px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface-muted);
|
||||||
|
background-image: linear-gradient(180deg, rgba(148, 163, 184, 0.15) 1px, transparent 1px);
|
||||||
|
background-size: 100% 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-bar {
|
||||||
|
flex: 1 0 28px;
|
||||||
|
min-width: 26px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-bar.selected .bar-track {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
box-shadow: 0 0 0 2px rgba(25, 118, 210, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-bar.selected .bar-value {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-track {
|
||||||
|
width: 100%;
|
||||||
|
height: 170px;
|
||||||
|
background: transparent;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column-reverse;
|
||||||
|
border-radius: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: height 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment {
|
||||||
|
width: 100%;
|
||||||
|
transition: height 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment.autorizadas {
|
||||||
|
background: #16a34a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment.no-autorizadas {
|
||||||
|
background: #dc2626;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment.pendientes {
|
||||||
|
background: #f59e0b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.segment:not(:first-child) {
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.65);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-label {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
min-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar-value {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
background: var(--color-card);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 2px 8px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
transition: opacity 0.2s ease, transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-bar:hover .bar-value {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram-bar:hover .bar-track {
|
||||||
|
border-color: var(--color-primary-soft);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-item {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 10px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
border-radius: 999px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.autorizadas {
|
||||||
|
background: #2e7d32;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.no-autorizadas {
|
||||||
|
background: #c62828;
|
||||||
|
}
|
||||||
|
|
||||||
|
.legend-dot.pendientes {
|
||||||
|
background: #f9a825;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-card {
|
||||||
|
margin-top: 18px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
background: var(--color-card);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.05rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-status {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-status.error {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-empty {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-table .estado-pill {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 4px 10px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: var(--color-surface-muted);
|
||||||
|
color: var(--color-text-muted);
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 720px) {
|
||||||
|
.histogram-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.histogram {
|
||||||
|
padding: 14px 10px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detalle-header {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,199 @@
|
|||||||
|
<div class="page-shell">
|
||||||
|
<div class="content-container">
|
||||||
|
<app-header
|
||||||
|
[title]="titulo"
|
||||||
|
subtitle="Resumen mensual del estado de autorizaciones"
|
||||||
|
badgeText="SALUD UT"
|
||||||
|
[showUserInfo]="true"
|
||||||
|
[userName]="authService.getCurrentUser()?.nombre_completo || null"
|
||||||
|
[userRole]="authService.getCurrentUser()?.nombre_rol || null"
|
||||||
|
[showLogout]="true"
|
||||||
|
(logout)="authService.logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="volverAtras()"
|
||||||
|
></app-header>
|
||||||
|
|
||||||
|
<div class="card controles-card">
|
||||||
|
<div class="form-row">
|
||||||
|
<label for="periodo">Periodo:</label>
|
||||||
|
<select id="periodo" [(ngModel)]="periodo" (change)="onPeriodoChange()">
|
||||||
|
<option value="dia">Dia</option>
|
||||||
|
<option value="semana">Semana</option>
|
||||||
|
<option value="mes">Mes</option>
|
||||||
|
<option value="anio">Año</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<ng-container *ngIf="periodo === 'dia' || periodo === 'semana'">
|
||||||
|
<label for="mesBase">Mes:</label>
|
||||||
|
<input
|
||||||
|
id="mesBase"
|
||||||
|
type="month"
|
||||||
|
[(ngModel)]="mesSeleccionado"
|
||||||
|
(change)="cargarEstadisticas()"
|
||||||
|
/>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="periodo === 'mes' || periodo === 'anio'">
|
||||||
|
<label for="anio">Año:</label>
|
||||||
|
<input
|
||||||
|
id="anio"
|
||||||
|
type="number"
|
||||||
|
min="2020"
|
||||||
|
[(ngModel)]="anioSeleccionado"
|
||||||
|
(change)="cargarEstadisticas()"
|
||||||
|
/>
|
||||||
|
</ng-container>
|
||||||
|
<button class="btn btn-primary" (click)="cargarEstadisticas()" [disabled]="isLoading">
|
||||||
|
{{ isLoading ? 'Cargando...' : 'Actualizar' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status error" *ngIf="errorMessage">{{ errorMessage }}</div>
|
||||||
|
|
||||||
|
<div class="stats-grid" *ngIf="data">
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Autorizadas</h3>
|
||||||
|
<p class="stat-value">{{ data.resumen.autorizadas }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>No autorizadas</h3>
|
||||||
|
<p class="stat-value">{{ data.resumen.no_autorizadas }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card">
|
||||||
|
<h3>Pendientes</h3>
|
||||||
|
<p class="stat-value">{{ data.resumen.pendientes }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="stat-card total">
|
||||||
|
<h3>Total</h3>
|
||||||
|
<p class="stat-value">{{ data.resumen.total }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card histogram-card" *ngIf="data">
|
||||||
|
<div class="histogram-header">
|
||||||
|
<h3>Autorizaciones por dia</h3>
|
||||||
|
<span class="histogram-range">
|
||||||
|
{{ data.rango.inicio }} a {{ data.rango.fin }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="histogram">
|
||||||
|
<div
|
||||||
|
class="histogram-bar"
|
||||||
|
*ngFor="let bucket of chartBuckets"
|
||||||
|
[class.selected]="bucket.key === selectedBucket?.key"
|
||||||
|
[attr.title]="
|
||||||
|
bucket.inicio +
|
||||||
|
' a ' +
|
||||||
|
bucket.fin +
|
||||||
|
' | total: ' +
|
||||||
|
(bucket.total || 0) +
|
||||||
|
' | autorizadas: ' +
|
||||||
|
(bucket.autorizadas || 0) +
|
||||||
|
' | no autorizadas: ' +
|
||||||
|
(bucket.no_autorizadas || 0) +
|
||||||
|
' | pendientes: ' +
|
||||||
|
(bucket.pendientes || 0)
|
||||||
|
"
|
||||||
|
(click)="seleccionarBucket(bucket)"
|
||||||
|
>
|
||||||
|
<div class="bar-value" *ngIf="bucket.total">{{ bucket.total }}</div>
|
||||||
|
<div class="bar-track">
|
||||||
|
<div class="bar" [style.height]="getBarHeight(bucket)">
|
||||||
|
<div
|
||||||
|
class="segment autorizadas"
|
||||||
|
*ngIf="bucket.autorizadas > 0"
|
||||||
|
[style.height]="getSegmentHeight(bucket, 'autorizadas')"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="segment no-autorizadas"
|
||||||
|
*ngIf="bucket.no_autorizadas > 0"
|
||||||
|
[style.height]="getSegmentHeight(bucket, 'no_autorizadas')"
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
class="segment pendientes"
|
||||||
|
*ngIf="tienePendientes && bucket.pendientes > 0"
|
||||||
|
[style.height]="getSegmentHeight(bucket, 'pendientes')"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bar-label">{{ bucket.label }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="legend">
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="legend-dot autorizadas"></span>
|
||||||
|
Autorizadas
|
||||||
|
</div>
|
||||||
|
<div class="legend-item">
|
||||||
|
<span class="legend-dot no-autorizadas"></span>
|
||||||
|
No autorizadas
|
||||||
|
</div>
|
||||||
|
<div class="legend-item" *ngIf="tienePendientes">
|
||||||
|
<span class="legend-dot pendientes"></span>
|
||||||
|
Pendientes
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card detalle-card" *ngIf="selectedBucket">
|
||||||
|
<div class="detalle-header">
|
||||||
|
<h4>Autorizaciones del {{ formatRangeLabel(selectedBucket.inicio, selectedBucket.fin) }}</h4>
|
||||||
|
<button class="btn btn-secondary btn-sm" type="button" (click)="limpiarDetalle()">
|
||||||
|
Cerrar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="detalle-status" *ngIf="isLoadingDetalle">
|
||||||
|
Cargando autorizaciones del rango...
|
||||||
|
</div>
|
||||||
|
<div class="detalle-status error" *ngIf="errorDetalle">{{ errorDetalle }}</div>
|
||||||
|
<div
|
||||||
|
class="detalle-empty"
|
||||||
|
*ngIf="!isLoadingDetalle && !errorDetalle && autorizacionesDetalle.length === 0"
|
||||||
|
>
|
||||||
|
No hay autorizaciones en este rango.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="detalle-status"
|
||||||
|
*ngIf="detalleLimitado && !isLoadingDetalle && !errorDetalle"
|
||||||
|
>
|
||||||
|
Mostrando hasta 500 autorizaciones.
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="table-container"
|
||||||
|
*ngIf="!isLoadingDetalle && !errorDetalle && autorizacionesDetalle.length > 0"
|
||||||
|
>
|
||||||
|
<table class="table detalle-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Numero</th>
|
||||||
|
<th>Interno</th>
|
||||||
|
<th>Paciente</th>
|
||||||
|
<th>CUPS</th>
|
||||||
|
<th>Estado</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let aut of autorizacionesDetalle">
|
||||||
|
<td class="numero-autorizacion">{{ aut.numero_autorizacion }}</td>
|
||||||
|
<td class="interno">{{ aut.interno }}</td>
|
||||||
|
<td class="nombre-paciente">{{ aut.nombre_paciente }}</td>
|
||||||
|
<td class="cup-codigo">{{ aut.cup_codigo }}</td>
|
||||||
|
<td class="estado">
|
||||||
|
<span class="estado-pill">
|
||||||
|
{{ getEstadoAutorizacionLabel(aut.estado_autorizacion) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@ -0,0 +1,457 @@
|
|||||||
|
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import {
|
||||||
|
AuthService,
|
||||||
|
EstadisticasAutorizaciones,
|
||||||
|
EstadisticasAutorizacionesDia,
|
||||||
|
} from '../../services/auth';
|
||||||
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
|
import { finalize } from 'rxjs/operators';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-estadisticas-autorizaciones',
|
||||||
|
standalone: true,
|
||||||
|
imports: [CommonModule, FormsModule, AppHeaderComponent],
|
||||||
|
templateUrl: './estadisticas-autorizaciones.html',
|
||||||
|
styleUrls: ['./estadisticas-autorizaciones.css']
|
||||||
|
})
|
||||||
|
export class EstadisticasAutorizacionesComponent implements OnInit {
|
||||||
|
titulo = 'Estadisticas de autorizaciones';
|
||||||
|
mesSeleccionado = '';
|
||||||
|
anioSeleccionado = '';
|
||||||
|
periodo: 'dia' | 'semana' | 'mes' | 'anio' = 'mes';
|
||||||
|
isLoading = false;
|
||||||
|
errorMessage: string | null = null;
|
||||||
|
data: EstadisticasAutorizaciones | null = null;
|
||||||
|
maxTotal = 0;
|
||||||
|
chartBuckets: ChartBucket[] = [];
|
||||||
|
selectedBucket: ChartBucket | null = null;
|
||||||
|
autorizacionesDetalle: any[] = [];
|
||||||
|
isLoadingDetalle = false;
|
||||||
|
errorDetalle: string | null = null;
|
||||||
|
detalleLimitado = false;
|
||||||
|
tienePendientes = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public authService: AuthService,
|
||||||
|
private router: Router,
|
||||||
|
private cdr: ChangeDetectorRef
|
||||||
|
) {}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
if (!this.authService.isAdministrador()) {
|
||||||
|
this.errorMessage = 'No tienes permisos para acceder a esta pagina.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hoy = new Date();
|
||||||
|
this.anioSeleccionado = String(hoy.getFullYear());
|
||||||
|
this.mesSeleccionado = this.formatMonthInput(hoy);
|
||||||
|
this.cargarEstadisticas();
|
||||||
|
}
|
||||||
|
|
||||||
|
onPeriodoChange(): void {
|
||||||
|
const hoy = new Date();
|
||||||
|
if (this.periodo === 'dia' || this.periodo === 'semana') {
|
||||||
|
if (!this.mesSeleccionado) {
|
||||||
|
this.mesSeleccionado = this.formatMonthInput(hoy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((this.periodo === 'mes' || this.periodo === 'anio') && !this.anioSeleccionado) {
|
||||||
|
this.anioSeleccionado = String(hoy.getFullYear());
|
||||||
|
}
|
||||||
|
this.cargarEstadisticas();
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarEstadisticas(): void {
|
||||||
|
this.errorMessage = null;
|
||||||
|
this.selectedBucket = null;
|
||||||
|
this.autorizacionesDetalle = [];
|
||||||
|
this.errorDetalle = null;
|
||||||
|
this.detalleLimitado = false;
|
||||||
|
|
||||||
|
let inicio = '';
|
||||||
|
let fin = '';
|
||||||
|
|
||||||
|
if (this.periodo === 'dia') {
|
||||||
|
if (!this.mesSeleccionado) {
|
||||||
|
this.errorMessage = 'Selecciona un mes para consultar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[inicio, fin] = this.getRangoMes(this.mesSeleccionado);
|
||||||
|
} else if (this.periodo === 'semana') {
|
||||||
|
if (!this.mesSeleccionado) {
|
||||||
|
this.errorMessage = 'Selecciona un mes para consultar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[inicio, fin] = this.getRangoMes(this.mesSeleccionado);
|
||||||
|
} else if (this.periodo === 'mes') {
|
||||||
|
if (!this.anioSeleccionado) {
|
||||||
|
this.errorMessage = 'Selecciona un año para consultar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[inicio, fin] = this.getRangoAnio(this.anioSeleccionado);
|
||||||
|
} else {
|
||||||
|
if (!this.anioSeleccionado) {
|
||||||
|
this.errorMessage = 'Selecciona un año para consultar.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
[inicio, fin] = this.getRangoAnios(this.anioSeleccionado);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!inicio || !fin) {
|
||||||
|
this.errorMessage = 'Rango de fechas invalido.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoading = true;
|
||||||
|
this.data = null;
|
||||||
|
this.maxTotal = 0;
|
||||||
|
|
||||||
|
this.authService
|
||||||
|
.getEstadisticasAutorizaciones(inicio, fin)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.isLoading = false;
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (resp) => {
|
||||||
|
this.data = resp;
|
||||||
|
this.chartBuckets = this.buildBuckets(resp?.dias || [], resp?.rango);
|
||||||
|
this.maxTotal = Math.max(
|
||||||
|
1,
|
||||||
|
...(this.chartBuckets.map((b) => Number(b.total) || 0))
|
||||||
|
);
|
||||||
|
this.tienePendientes = Number(resp?.resumen?.pendientes) > 0;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorMessage =
|
||||||
|
err?.error?.error || 'Error consultando estadisticas.';
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
volverAtras(): void {
|
||||||
|
this.router.navigate(['/dashboard']);
|
||||||
|
}
|
||||||
|
|
||||||
|
getBarHeight(bucket: ChartBucket): string {
|
||||||
|
if (!this.maxTotal) return '0%';
|
||||||
|
const total = Number(bucket.total) || 0;
|
||||||
|
const percent = Math.max(0, Math.min(100, (total / this.maxTotal) * 100));
|
||||||
|
return `${percent}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSegmentHeight(
|
||||||
|
bucket: ChartBucket,
|
||||||
|
key: 'autorizadas' | 'no_autorizadas' | 'pendientes'
|
||||||
|
): string {
|
||||||
|
const total = Number(bucket.total) || 0;
|
||||||
|
if (!total) return '0%';
|
||||||
|
const value = Number(bucket[key]) || 0;
|
||||||
|
const percent = Math.max(0, Math.min(100, (value / total) * 100));
|
||||||
|
return `${percent}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
seleccionarBucket(bucket: ChartBucket): void {
|
||||||
|
if (!bucket?.inicio || !bucket?.fin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.selectedBucket = bucket;
|
||||||
|
this.autorizacionesDetalle = [];
|
||||||
|
this.errorDetalle = null;
|
||||||
|
this.detalleLimitado = false;
|
||||||
|
this.isLoadingDetalle = false;
|
||||||
|
|
||||||
|
const total = Number(bucket.total) || 0;
|
||||||
|
if (!total) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isLoadingDetalle = true;
|
||||||
|
|
||||||
|
this.authService
|
||||||
|
.getAutorizacionesPorFecha(bucket.inicio, bucket.fin, 500, 0)
|
||||||
|
.pipe(
|
||||||
|
finalize(() => {
|
||||||
|
this.isLoadingDetalle = false;
|
||||||
|
this.cdr.markForCheck();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.subscribe({
|
||||||
|
next: (resp) => {
|
||||||
|
this.autorizacionesDetalle = resp || [];
|
||||||
|
this.detalleLimitado = this.autorizacionesDetalle.length >= 500;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
this.errorDetalle =
|
||||||
|
err?.error?.error || 'Error consultando autorizaciones del rango.';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
limpiarDetalle(): void {
|
||||||
|
this.selectedBucket = null;
|
||||||
|
this.autorizacionesDetalle = [];
|
||||||
|
this.errorDetalle = null;
|
||||||
|
this.detalleLimitado = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatDateLabel(fecha: string): string {
|
||||||
|
const date = new Date(fecha);
|
||||||
|
if (Number.isNaN(date.getTime())) return fecha;
|
||||||
|
return date.toLocaleDateString('es-CO', {
|
||||||
|
weekday: 'short',
|
||||||
|
year: 'numeric',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatRangeLabel(inicio: string, fin: string): string {
|
||||||
|
if (inicio === fin) {
|
||||||
|
return this.formatDateLabel(inicio);
|
||||||
|
}
|
||||||
|
return `${this.formatDateLabel(inicio)} - ${this.formatDateLabel(fin)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
getEstadoAutorizacionLabel(estado: string | null | undefined): string {
|
||||||
|
const normalizado = String(estado || 'pendiente').toLowerCase();
|
||||||
|
if (normalizado === 'autorizado') {
|
||||||
|
return 'Autorizado';
|
||||||
|
}
|
||||||
|
if (normalizado === 'no_autorizado') {
|
||||||
|
return 'No autorizado';
|
||||||
|
}
|
||||||
|
return 'Pendiente';
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRangoMes(mes: string): [string, string] {
|
||||||
|
const [yearStr, monthStr] = mes.split('-');
|
||||||
|
const year = Number(yearStr);
|
||||||
|
const monthIndex = Number(monthStr) - 1;
|
||||||
|
const inicio = new Date(year, monthIndex, 1);
|
||||||
|
const fin = new Date(year, monthIndex + 1, 0);
|
||||||
|
|
||||||
|
const hoy = new Date();
|
||||||
|
const esMesActual =
|
||||||
|
hoy.getFullYear() === year && hoy.getMonth() === monthIndex;
|
||||||
|
|
||||||
|
const fechaFin = esMesActual ? hoy : fin;
|
||||||
|
return [this.formatDate(inicio), this.formatDate(fechaFin)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRangoAnio(anio: string): [string, string] {
|
||||||
|
const year = Number(anio);
|
||||||
|
if (!year) {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
const inicio = new Date(year, 0, 1);
|
||||||
|
const fin = new Date(year, 11, 31);
|
||||||
|
const hoy = new Date();
|
||||||
|
const esAnioActual = hoy.getFullYear() === year;
|
||||||
|
const fechaFin = esAnioActual ? hoy : fin;
|
||||||
|
return [this.formatDate(inicio), this.formatDate(fechaFin)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRangoAnios(anio: string): [string, string] {
|
||||||
|
const year = Number(anio);
|
||||||
|
if (!year) {
|
||||||
|
return ['', ''];
|
||||||
|
}
|
||||||
|
const inicio = new Date(year - 4, 0, 1);
|
||||||
|
const fin = new Date(year, 11, 31);
|
||||||
|
const hoy = new Date();
|
||||||
|
const fechaFin = hoy < fin ? hoy : fin;
|
||||||
|
return [this.formatDate(inicio), this.formatDate(fechaFin)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatDate(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 formatMonthInput(date: Date): string {
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||||
|
return `${year}-${month}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private buildBuckets(
|
||||||
|
dias: EstadisticasAutorizacionesDia[],
|
||||||
|
rango?: { inicio: string; fin: string }
|
||||||
|
): ChartBucket[] {
|
||||||
|
if (!rango) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.periodo === 'dia') {
|
||||||
|
return dias.map((dia) => {
|
||||||
|
const date = new Date(dia.fecha);
|
||||||
|
const label = Number.isNaN(date.getTime())
|
||||||
|
? ''
|
||||||
|
: String(date.getDate());
|
||||||
|
return {
|
||||||
|
key: dia.fecha,
|
||||||
|
label,
|
||||||
|
inicio: dia.fecha,
|
||||||
|
fin: dia.fecha,
|
||||||
|
total: Number(dia.total) || 0,
|
||||||
|
autorizadas: Number(dia.autorizadas) || 0,
|
||||||
|
no_autorizadas: Number(dia.no_autorizadas) || 0,
|
||||||
|
pendientes: Number(dia.pendientes) || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const rangoInicio = new Date(rango.inicio);
|
||||||
|
const rangoFin = new Date(rango.fin);
|
||||||
|
|
||||||
|
if (this.periodo === 'semana') {
|
||||||
|
const buckets = new Map<string, ChartBucket>();
|
||||||
|
dias.forEach((dia) => {
|
||||||
|
const date = new Date(dia.fecha);
|
||||||
|
if (Number.isNaN(date.getTime())) return;
|
||||||
|
const weekStart = this.getWeekStart(date);
|
||||||
|
const weekEnd = this.addDays(weekStart, 6);
|
||||||
|
const key = this.formatDate(weekStart);
|
||||||
|
if (!buckets.has(key)) {
|
||||||
|
const inicio = this.maxDate(weekStart, rangoInicio);
|
||||||
|
const fin = this.minDate(weekEnd, rangoFin);
|
||||||
|
buckets.set(key, {
|
||||||
|
key,
|
||||||
|
label: this.formatWeekLabel(inicio, fin),
|
||||||
|
inicio: this.formatDate(inicio),
|
||||||
|
fin: this.formatDate(fin),
|
||||||
|
total: 0,
|
||||||
|
autorizadas: 0,
|
||||||
|
no_autorizadas: 0,
|
||||||
|
pendientes: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const bucket = buckets.get(key)!;
|
||||||
|
bucket.total += Number(dia.total) || 0;
|
||||||
|
bucket.autorizadas += Number(dia.autorizadas) || 0;
|
||||||
|
bucket.no_autorizadas += Number(dia.no_autorizadas) || 0;
|
||||||
|
bucket.pendientes += Number(dia.pendientes) || 0;
|
||||||
|
});
|
||||||
|
return Array.from(buckets.values()).sort((a, b) =>
|
||||||
|
a.inicio.localeCompare(b.inicio)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.periodo === 'mes') {
|
||||||
|
const buckets = new Map<string, ChartBucket>();
|
||||||
|
dias.forEach((dia) => {
|
||||||
|
const date = new Date(dia.fecha);
|
||||||
|
if (Number.isNaN(date.getTime())) return;
|
||||||
|
const key = `${date.getFullYear()}-${date.getMonth()}`;
|
||||||
|
if (!buckets.has(key)) {
|
||||||
|
const inicio = new Date(date.getFullYear(), date.getMonth(), 1);
|
||||||
|
const fin = new Date(date.getFullYear(), date.getMonth() + 1, 0);
|
||||||
|
const inicioClamp = this.maxDate(inicio, rangoInicio);
|
||||||
|
const finClamp = this.minDate(fin, rangoFin);
|
||||||
|
buckets.set(key, {
|
||||||
|
key,
|
||||||
|
label: date.toLocaleDateString('es-CO', { month: 'short' }),
|
||||||
|
inicio: this.formatDate(inicioClamp),
|
||||||
|
fin: this.formatDate(finClamp),
|
||||||
|
total: 0,
|
||||||
|
autorizadas: 0,
|
||||||
|
no_autorizadas: 0,
|
||||||
|
pendientes: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const bucket = buckets.get(key)!;
|
||||||
|
bucket.total += Number(dia.total) || 0;
|
||||||
|
bucket.autorizadas += Number(dia.autorizadas) || 0;
|
||||||
|
bucket.no_autorizadas += Number(dia.no_autorizadas) || 0;
|
||||||
|
bucket.pendientes += Number(dia.pendientes) || 0;
|
||||||
|
});
|
||||||
|
return Array.from(buckets.values()).sort((a, b) =>
|
||||||
|
a.inicio.localeCompare(b.inicio)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buckets = new Map<string, ChartBucket>();
|
||||||
|
dias.forEach((dia) => {
|
||||||
|
const date = new Date(dia.fecha);
|
||||||
|
if (Number.isNaN(date.getTime())) return;
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const key = String(year);
|
||||||
|
if (!buckets.has(key)) {
|
||||||
|
const inicio = new Date(year, 0, 1);
|
||||||
|
const fin = new Date(year, 11, 31);
|
||||||
|
const inicioClamp = this.maxDate(inicio, rangoInicio);
|
||||||
|
const finClamp = this.minDate(fin, rangoFin);
|
||||||
|
buckets.set(key, {
|
||||||
|
key,
|
||||||
|
label: key,
|
||||||
|
inicio: this.formatDate(inicioClamp),
|
||||||
|
fin: this.formatDate(finClamp),
|
||||||
|
total: 0,
|
||||||
|
autorizadas: 0,
|
||||||
|
no_autorizadas: 0,
|
||||||
|
pendientes: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const bucket = buckets.get(key)!;
|
||||||
|
bucket.total += Number(dia.total) || 0;
|
||||||
|
bucket.autorizadas += Number(dia.autorizadas) || 0;
|
||||||
|
bucket.no_autorizadas += Number(dia.no_autorizadas) || 0;
|
||||||
|
bucket.pendientes += Number(dia.pendientes) || 0;
|
||||||
|
});
|
||||||
|
return Array.from(buckets.values()).sort((a, b) =>
|
||||||
|
a.inicio.localeCompare(b.inicio)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getWeekStart(date: Date): Date {
|
||||||
|
const base = new Date(date);
|
||||||
|
const day = base.getDay();
|
||||||
|
const diffToMonday = (day + 6) % 7;
|
||||||
|
base.setDate(base.getDate() - diffToMonday);
|
||||||
|
base.setHours(0, 0, 0, 0);
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addDays(date: Date, days: number): Date {
|
||||||
|
const next = new Date(date);
|
||||||
|
next.setDate(next.getDate() + days);
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
|
||||||
|
private maxDate(a: Date, b: Date): Date {
|
||||||
|
return a > b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private minDate(a: Date, b: Date): Date {
|
||||||
|
return a < b ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private formatWeekLabel(inicio: Date, fin: Date): string {
|
||||||
|
const inicioDia = inicio.getDate();
|
||||||
|
const finDia = fin.getDate();
|
||||||
|
return `${inicioDia}-${finDia}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ChartBucket = {
|
||||||
|
key: string;
|
||||||
|
label: string;
|
||||||
|
inicio: string;
|
||||||
|
fin: string;
|
||||||
|
total: number;
|
||||||
|
autorizadas: number;
|
||||||
|
no_autorizadas: number;
|
||||||
|
pendientes: number;
|
||||||
|
};
|
||||||
@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
.subtitle {
|
.subtitle {
|
||||||
margin: 4px 0 0;
|
margin: 4px 0 0;
|
||||||
color: #666666;
|
color: var(--color-text-muted);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,6 +42,7 @@
|
|||||||
.form-row label {
|
.form-row label {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
color: var(--color-text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row input,
|
.form-row input,
|
||||||
@ -49,8 +50,10 @@
|
|||||||
.form-row textarea {
|
.form-row textarea {
|
||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
border: 1px solid #ccc;
|
border: 1px solid var(--color-input-border);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
color: var(--color-text-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-row input:focus,
|
.form-row input:focus,
|
||||||
@ -67,7 +70,7 @@
|
|||||||
|
|
||||||
.hint {
|
.hint {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #777777;
|
color: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tabla */
|
/* Tabla */
|
||||||
@ -82,17 +85,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.tabla-usuarios-wrapper::-webkit-scrollbar-track {
|
.tabla-usuarios-wrapper::-webkit-scrollbar-track {
|
||||||
background: #f1f1f1;
|
background: var(--color-surface-muted);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb {
|
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb {
|
||||||
background: #c0c0c0;
|
background: var(--color-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb:hover {
|
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb:hover {
|
||||||
background: #a0a0a0;
|
background: var(--color-text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tabla-usuarios thead {
|
.tabla-usuarios thead {
|
||||||
@ -106,6 +109,43 @@
|
|||||||
padding: 8px 10px;
|
padding: 8px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-inline {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 120px;
|
||||||
|
padding: 6px 8px;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid var(--color-input-border);
|
||||||
|
font-size: 0.85rem;
|
||||||
|
background: var(--color-input-bg);
|
||||||
|
color: var(--color-text-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-inline:focus {
|
||||||
|
border-color: #1976d2;
|
||||||
|
box-shadow: 0 0 0 1px rgba(25, 118, 210, 0.15);
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.acciones-inline {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-status {
|
||||||
|
margin-top: 4px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-status.ok {
|
||||||
|
color: var(--color-success);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inline-status.error {
|
||||||
|
color: var(--color-error);
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.usuarios-grid {
|
.usuarios-grid {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
|
|||||||
@ -3,11 +3,15 @@
|
|||||||
<app-header
|
<app-header
|
||||||
title="SALUD UT"
|
title="SALUD UT"
|
||||||
subtitle="Gestión de Usuarios"
|
subtitle="Gestión de Usuarios"
|
||||||
|
badgeText="SALUD UT"
|
||||||
[showUserInfo]="true"
|
[showUserInfo]="true"
|
||||||
[userName]="getNombreUsuario()"
|
[userName]="getNombreUsuario()"
|
||||||
[userRole]="getNombreRolFormateado()"
|
[userRole]="getNombreRolFormateado()"
|
||||||
[showLogout]="true"
|
[showLogout]="true"
|
||||||
(logout)="logout()"
|
(logout)="logout()"
|
||||||
|
[showBack]="true"
|
||||||
|
backLabel="Volver"
|
||||||
|
(back)="volverDashboard()"
|
||||||
></app-header>
|
></app-header>
|
||||||
|
|
||||||
<header class="usuarios-header">
|
<header class="usuarios-header">
|
||||||
@ -17,9 +21,6 @@
|
|||||||
Crear, activar y desactivar usuarios del sistema
|
Crear, activar y desactivar usuarios del sistema
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-secondary" (click)="volverDashboard()">
|
|
||||||
Volver al dashboard
|
|
||||||
</button>
|
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<section class="usuarios-grid">
|
<section class="usuarios-grid">
|
||||||
@ -57,19 +58,6 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-row" *ngIf="nuevoUsuario.id_rol === 2">
|
|
||||||
<label>Sedes (códigos):</label>
|
|
||||||
<textarea
|
|
||||||
rows="2"
|
|
||||||
[(ngModel)]="sedesTexto"
|
|
||||||
placeholder="Ej: 113, 148, 205 (separadas por coma)"
|
|
||||||
></textarea>
|
|
||||||
<small class="hint">
|
|
||||||
Solo para rol <strong>administrativo_sede</strong>. Usa los códigos
|
|
||||||
de establecimiento, separados por coma.
|
|
||||||
</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-row acciones">
|
<div class="form-row acciones">
|
||||||
<button class="btn btn-primary" (click)="crearUsuario()" [disabled]="creando">
|
<button class="btn btn-primary" (click)="crearUsuario()" [disabled]="creando">
|
||||||
{{ creando ? 'Creando...' : 'Crear usuario' }}
|
{{ creando ? 'Creando...' : 'Crear usuario' }}
|
||||||
@ -160,9 +148,29 @@
|
|||||||
<tr *ngFor="let a of autorizantes; trackBy: trackByAutorizante">
|
<tr *ngFor="let a of autorizantes; trackBy: trackByAutorizante">
|
||||||
<td>{{ a.numero_documento }}</td>
|
<td>{{ a.numero_documento }}</td>
|
||||||
<td>{{ a.tipo_documento }}</td>
|
<td>{{ a.tipo_documento }}</td>
|
||||||
<td>{{ a.nombre }}</td>
|
<td>
|
||||||
<td>{{ a.telefono || '-' }}</td>
|
<input
|
||||||
<td>{{ a.cargo || '-' }}</td>
|
class="input-inline"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="autorizanteEdicion[a.numero_documento].nombre"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="input-inline"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="autorizanteEdicion[a.numero_documento].telefono"
|
||||||
|
placeholder="-"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="input-inline"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="autorizanteEdicion[a.numero_documento].cargo"
|
||||||
|
placeholder="-"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
class="badge"
|
class="badge"
|
||||||
@ -173,6 +181,18 @@
|
|||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="acciones-inline">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
(click)="guardarAutorizante(a)"
|
||||||
|
[disabled]="autorizanteEdicion[a.numero_documento].guardando"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
autorizanteEdicion[a.numero_documento].guardando
|
||||||
|
? 'Guardando...'
|
||||||
|
: 'Guardar'
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="a.activo"
|
*ngIf="a.activo"
|
||||||
class="btn btn-danger btn-sm"
|
class="btn btn-danger btn-sm"
|
||||||
@ -187,6 +207,19 @@
|
|||||||
>
|
>
|
||||||
Activar
|
Activar
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="inline-status ok"
|
||||||
|
*ngIf="autorizanteEdicion[a.numero_documento]?.mensaje"
|
||||||
|
>
|
||||||
|
{{ autorizanteEdicion[a.numero_documento].mensaje }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="inline-status error"
|
||||||
|
*ngIf="autorizanteEdicion[a.numero_documento]?.error"
|
||||||
|
>
|
||||||
|
{{ autorizanteEdicion[a.numero_documento].error }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -210,6 +243,7 @@
|
|||||||
<th>Nombre</th>
|
<th>Nombre</th>
|
||||||
<th>Email</th>
|
<th>Email</th>
|
||||||
<th>Rol</th>
|
<th>Rol</th>
|
||||||
|
<th>Nueva contrasena</th>
|
||||||
<th>Estado</th>
|
<th>Estado</th>
|
||||||
<th>Último login</th>
|
<th>Último login</th>
|
||||||
<th>Acciones</th>
|
<th>Acciones</th>
|
||||||
@ -217,10 +251,45 @@
|
|||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr *ngFor="let u of usuarios; trackBy: trackByUsuario">
|
<tr *ngFor="let u of usuarios; trackBy: trackByUsuario">
|
||||||
<td>{{ u.username }}</td>
|
<td>
|
||||||
<td>{{ u.nombre_completo }}</td>
|
<input
|
||||||
<td>{{ u.email }}</td>
|
class="input-inline"
|
||||||
<td>{{ u.nombre_rol }}</td>
|
type="text"
|
||||||
|
[(ngModel)]="usuarioEdicion[u.id_usuario].username"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="input-inline"
|
||||||
|
type="text"
|
||||||
|
[(ngModel)]="usuarioEdicion[u.id_usuario].nombre_completo"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="input-inline"
|
||||||
|
type="email"
|
||||||
|
[(ngModel)]="usuarioEdicion[u.id_usuario].email"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<select
|
||||||
|
class="input-inline"
|
||||||
|
[(ngModel)]="usuarioEdicion[u.id_usuario].id_rol"
|
||||||
|
>
|
||||||
|
<option *ngFor="let r of roles" [ngValue]="r.id_rol">
|
||||||
|
{{ r.nombre_rol }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<input
|
||||||
|
class="input-inline"
|
||||||
|
type="password"
|
||||||
|
[(ngModel)]="usuarioEdicion[u.id_usuario].password"
|
||||||
|
placeholder="Nueva contrasena"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span
|
<span
|
||||||
class="badge"
|
class="badge"
|
||||||
@ -234,6 +303,18 @@
|
|||||||
{{ u.ultimo_login ? (u.ultimo_login | date:'yyyy-MM-dd HH:mm') : '—' }}
|
{{ u.ultimo_login ? (u.ultimo_login | date:'yyyy-MM-dd HH:mm') : '—' }}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
<div class="acciones-inline">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary btn-sm"
|
||||||
|
(click)="guardarUsuario(u)"
|
||||||
|
[disabled]="usuarioEdicion[u.id_usuario].guardando"
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
usuarioEdicion[u.id_usuario].guardando
|
||||||
|
? 'Guardando...'
|
||||||
|
: 'Guardar'
|
||||||
|
}}
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
*ngIf="u.activo"
|
*ngIf="u.activo"
|
||||||
class="btn btn-danger btn-sm"
|
class="btn btn-danger btn-sm"
|
||||||
@ -248,6 +329,19 @@
|
|||||||
>
|
>
|
||||||
Activar
|
Activar
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="inline-status ok"
|
||||||
|
*ngIf="usuarioEdicion[u.id_usuario]?.mensaje"
|
||||||
|
>
|
||||||
|
{{ usuarioEdicion[u.id_usuario].mensaje }}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="inline-status error"
|
||||||
|
*ngIf="usuarioEdicion[u.id_usuario]?.error"
|
||||||
|
>
|
||||||
|
{{ usuarioEdicion[u.id_usuario].error }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@ -3,17 +3,39 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { FormsModule } from '@angular/forms';
|
import { FormsModule } from '@angular/forms';
|
||||||
import {
|
import {
|
||||||
AuthService,
|
AuthService,
|
||||||
|
ActualizarUsuarioPayload,
|
||||||
RegisterRequest,
|
RegisterRequest,
|
||||||
Rol
|
Rol
|
||||||
} from '../../services/auth';
|
} from '../../services/auth';
|
||||||
import {
|
import {
|
||||||
Autorizante,
|
Autorizante,
|
||||||
|
ActualizarAutorizantePayload,
|
||||||
CrearAutorizantePayload,
|
CrearAutorizantePayload,
|
||||||
PacienteService
|
PacienteService
|
||||||
} from '../../services/paciente';
|
} from '../../services/paciente';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||||
|
|
||||||
|
interface AutorizanteEdicion {
|
||||||
|
nombre: string;
|
||||||
|
telefono: string;
|
||||||
|
cargo: string;
|
||||||
|
guardando: boolean;
|
||||||
|
mensaje: string | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UsuarioEdicion {
|
||||||
|
username: string;
|
||||||
|
nombre_completo: string;
|
||||||
|
email: string;
|
||||||
|
id_rol: number;
|
||||||
|
password: string;
|
||||||
|
guardando: boolean;
|
||||||
|
mensaje: string | null;
|
||||||
|
error: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-usuarios',
|
selector: 'app-usuarios',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
@ -26,6 +48,8 @@ export class UsuariosComponent implements OnInit {
|
|||||||
usuarios: any[] = [];
|
usuarios: any[] = [];
|
||||||
roles: Rol[] = [];
|
roles: Rol[] = [];
|
||||||
autorizantes: Autorizante[] = [];
|
autorizantes: Autorizante[] = [];
|
||||||
|
autorizanteEdicion: Record<number, AutorizanteEdicion> = {};
|
||||||
|
usuarioEdicion: Record<number, UsuarioEdicion> = {};
|
||||||
|
|
||||||
cargandoUsuarios = false;
|
cargandoUsuarios = false;
|
||||||
errorUsuarios: string | null = null;
|
errorUsuarios: string | null = null;
|
||||||
@ -43,8 +67,6 @@ export class UsuariosComponent implements OnInit {
|
|||||||
mensajeAutorizanteOk: string | null = null;
|
mensajeAutorizanteOk: string | null = null;
|
||||||
mensajeAutorizanteError: string | null = null;
|
mensajeAutorizanteError: string | null = null;
|
||||||
|
|
||||||
// para administrativos, códigos separados por coma
|
|
||||||
sedesTexto = '';
|
|
||||||
|
|
||||||
nuevoUsuario: RegisterRequest = {
|
nuevoUsuario: RegisterRequest = {
|
||||||
username: '',
|
username: '',
|
||||||
@ -134,6 +156,7 @@ export class UsuariosComponent implements OnInit {
|
|||||||
this.authService.getUsuarios(this.limiteUsuarios, 0).subscribe({
|
this.authService.getUsuarios(this.limiteUsuarios, 0).subscribe({
|
||||||
next: (usuarios) => {
|
next: (usuarios) => {
|
||||||
this.usuarios = usuarios;
|
this.usuarios = usuarios;
|
||||||
|
this.prepararEdicionUsuarios();
|
||||||
this.avisoUsuarios = usuarios.length >= this.limiteUsuarios;
|
this.avisoUsuarios = usuarios.length >= this.limiteUsuarios;
|
||||||
this.cargandoUsuarios = false;
|
this.cargandoUsuarios = false;
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
@ -161,17 +184,7 @@ export class UsuariosComponent implements OnInit {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Si es administrativo de sede (id_rol = 2) procesamos las sedes
|
|
||||||
if (this.nuevoUsuario.id_rol === 2) {
|
|
||||||
const codigos = this.sedesTexto
|
|
||||||
.split(',')
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(s => s.length > 0);
|
|
||||||
|
|
||||||
this.nuevoUsuario.sedes = codigos;
|
|
||||||
} else {
|
|
||||||
this.nuevoUsuario.sedes = [];
|
this.nuevoUsuario.sedes = [];
|
||||||
}
|
|
||||||
|
|
||||||
this.creando = true;
|
this.creando = true;
|
||||||
|
|
||||||
@ -189,7 +202,6 @@ export class UsuariosComponent implements OnInit {
|
|||||||
id_rol: 2,
|
id_rol: 2,
|
||||||
sedes: []
|
sedes: []
|
||||||
};
|
};
|
||||||
this.sedesTexto = '';
|
|
||||||
|
|
||||||
// Recargar lista sin refrescar la página
|
// Recargar lista sin refrescar la página
|
||||||
this.cargarUsuarios();
|
this.cargarUsuarios();
|
||||||
@ -290,6 +302,7 @@ export class UsuariosComponent implements OnInit {
|
|||||||
this.pacienteService.obtenerAutorizantesAdmin().subscribe({
|
this.pacienteService.obtenerAutorizantesAdmin().subscribe({
|
||||||
next: (autorizantes) => {
|
next: (autorizantes) => {
|
||||||
this.autorizantes = autorizantes;
|
this.autorizantes = autorizantes;
|
||||||
|
this.prepararEdicionAutorizantes();
|
||||||
this.cargandoAutorizantes = false;
|
this.cargandoAutorizantes = false;
|
||||||
this.cdr.detectChanges();
|
this.cdr.detectChanges();
|
||||||
},
|
},
|
||||||
@ -328,4 +341,219 @@ export class UsuariosComponent implements OnInit {
|
|||||||
trackByAutorizante(_index: number, autorizante: Autorizante): number | string {
|
trackByAutorizante(_index: number, autorizante: Autorizante): number | string {
|
||||||
return autorizante?.numero_documento ?? _index;
|
return autorizante?.numero_documento ?? _index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private prepararEdicionAutorizantes(): void {
|
||||||
|
const edicion: Record<number, AutorizanteEdicion> = {};
|
||||||
|
this.autorizantes.forEach((a) => {
|
||||||
|
edicion[a.numero_documento] = {
|
||||||
|
nombre: a.nombre || '',
|
||||||
|
telefono: a.telefono || '',
|
||||||
|
cargo: a.cargo || '',
|
||||||
|
guardando: false,
|
||||||
|
mensaje: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.autorizanteEdicion = edicion;
|
||||||
|
}
|
||||||
|
|
||||||
|
private prepararEdicionUsuarios(): void {
|
||||||
|
const edicion: Record<number, UsuarioEdicion> = {};
|
||||||
|
this.usuarios.forEach((u) => {
|
||||||
|
edicion[u.id_usuario] = {
|
||||||
|
username: u.username || '',
|
||||||
|
nombre_completo: u.nombre_completo || '',
|
||||||
|
email: u.email || '',
|
||||||
|
id_rol: u.id_rol || 0,
|
||||||
|
password: '',
|
||||||
|
guardando: false,
|
||||||
|
mensaje: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
this.usuarioEdicion = edicion;
|
||||||
|
}
|
||||||
|
|
||||||
|
guardarAutorizante(a: Autorizante): void {
|
||||||
|
const edicion = this.autorizanteEdicion[a.numero_documento];
|
||||||
|
if (!edicion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edicion.mensaje = null;
|
||||||
|
edicion.error = null;
|
||||||
|
|
||||||
|
const nombreNuevo = (edicion.nombre || '').trim();
|
||||||
|
const telefonoNuevo = (edicion.telefono || '').trim();
|
||||||
|
const cargoNuevo = (edicion.cargo || '').trim();
|
||||||
|
const nombreActual = (a.nombre || '').trim();
|
||||||
|
const telefonoActual = (a.telefono || '').trim();
|
||||||
|
const cargoActual = (a.cargo || '').trim();
|
||||||
|
|
||||||
|
if (!nombreNuevo) {
|
||||||
|
edicion.error = 'El nombre no puede quedar vacio.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
nombreNuevo === nombreActual &&
|
||||||
|
telefonoNuevo === telefonoActual &&
|
||||||
|
cargoNuevo === cargoActual
|
||||||
|
) {
|
||||||
|
edicion.mensaje = 'Sin cambios.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: ActualizarAutorizantePayload = {};
|
||||||
|
if (nombreNuevo !== nombreActual) {
|
||||||
|
payload.nombre = nombreNuevo;
|
||||||
|
}
|
||||||
|
if (telefonoNuevo !== telefonoActual) {
|
||||||
|
payload.telefono = telefonoNuevo.length ? telefonoNuevo : null;
|
||||||
|
}
|
||||||
|
if (cargoNuevo !== cargoActual) {
|
||||||
|
payload.cargo = cargoNuevo.length ? cargoNuevo : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
edicion.guardando = true;
|
||||||
|
|
||||||
|
this.pacienteService.actualizarDatosAutorizante(a.numero_documento, payload).subscribe({
|
||||||
|
next: (resp: any) => {
|
||||||
|
const actualizado = resp?.autorizante;
|
||||||
|
if (actualizado) {
|
||||||
|
a.nombre = actualizado.nombre;
|
||||||
|
a.telefono = actualizado.telefono;
|
||||||
|
a.cargo = actualizado.cargo;
|
||||||
|
} else {
|
||||||
|
a.nombre = payload.nombre || a.nombre;
|
||||||
|
a.telefono = payload.telefono ?? undefined;
|
||||||
|
a.cargo = payload.cargo ?? undefined;
|
||||||
|
}
|
||||||
|
edicion.nombre = a.nombre || '';
|
||||||
|
edicion.telefono = a.telefono || '';
|
||||||
|
edicion.cargo = a.cargo || '';
|
||||||
|
edicion.guardando = false;
|
||||||
|
edicion.mensaje = 'Actualizado.';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
edicion.guardando = false;
|
||||||
|
edicion.error = err?.error?.error || 'Error actualizando autorizante.';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
guardarUsuario(u: any): void {
|
||||||
|
const edicion = this.usuarioEdicion[u.id_usuario];
|
||||||
|
if (!edicion) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edicion.mensaje = null;
|
||||||
|
edicion.error = null;
|
||||||
|
|
||||||
|
const usernameNuevo = (edicion.username || '').trim();
|
||||||
|
const nombreNuevo = (edicion.nombre_completo || '').trim();
|
||||||
|
const emailNuevo = (edicion.email || '').trim();
|
||||||
|
const rolNuevo = Number(edicion.id_rol);
|
||||||
|
const usernameActual = (u.username || '').trim();
|
||||||
|
const nombreActual = (u.nombre_completo || '').trim();
|
||||||
|
const emailActual = (u.email || '').trim();
|
||||||
|
const rolActual = Number(u.id_rol);
|
||||||
|
const passwordNueva = edicion.password || '';
|
||||||
|
|
||||||
|
if (!usernameNuevo) {
|
||||||
|
edicion.error = 'El usuario no puede quedar vacio.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nombreNuevo) {
|
||||||
|
edicion.error = 'El nombre no puede quedar vacio.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emailNuevo) {
|
||||||
|
edicion.error = 'El email no puede quedar vacio.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Number.isFinite(rolNuevo) || rolNuevo <= 0) {
|
||||||
|
edicion.error = 'El rol no es valido.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload: ActualizarUsuarioPayload = {};
|
||||||
|
if (usernameNuevo !== usernameActual) {
|
||||||
|
payload.username = usernameNuevo;
|
||||||
|
}
|
||||||
|
if (nombreNuevo !== nombreActual) {
|
||||||
|
payload.nombre_completo = nombreNuevo;
|
||||||
|
}
|
||||||
|
if (emailNuevo !== emailActual) {
|
||||||
|
payload.email = emailNuevo;
|
||||||
|
}
|
||||||
|
if (rolNuevo !== rolActual) {
|
||||||
|
payload.id_rol = rolNuevo;
|
||||||
|
}
|
||||||
|
if (passwordNueva) {
|
||||||
|
payload.password = passwordNueva;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!payload.username && !payload.password && !payload.nombre_completo && !payload.email && !payload.id_rol) {
|
||||||
|
edicion.mensaje = 'Sin cambios.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
edicion.guardando = true;
|
||||||
|
|
||||||
|
this.authService.actualizarUsuario(u.id_usuario, payload).subscribe({
|
||||||
|
next: (resp: any) => {
|
||||||
|
const actualizado = resp?.usuario;
|
||||||
|
if (actualizado?.username) {
|
||||||
|
u.username = actualizado.username;
|
||||||
|
} else if (payload.username) {
|
||||||
|
u.username = payload.username;
|
||||||
|
}
|
||||||
|
if (actualizado?.nombre_completo) {
|
||||||
|
u.nombre_completo = actualizado.nombre_completo;
|
||||||
|
} else if (payload.nombre_completo) {
|
||||||
|
u.nombre_completo = payload.nombre_completo;
|
||||||
|
}
|
||||||
|
if (actualizado?.email) {
|
||||||
|
u.email = actualizado.email;
|
||||||
|
} else if (payload.email) {
|
||||||
|
u.email = payload.email;
|
||||||
|
}
|
||||||
|
if (actualizado?.id_rol) {
|
||||||
|
u.id_rol = actualizado.id_rol;
|
||||||
|
const rolEncontrado = this.roles.find((r) => r.id_rol === actualizado.id_rol);
|
||||||
|
if (rolEncontrado) {
|
||||||
|
u.nombre_rol = rolEncontrado.nombre_rol;
|
||||||
|
}
|
||||||
|
} else if (payload.id_rol) {
|
||||||
|
u.id_rol = payload.id_rol;
|
||||||
|
const rolEncontrado = this.roles.find((r) => r.id_rol === payload.id_rol);
|
||||||
|
if (rolEncontrado) {
|
||||||
|
u.nombre_rol = rolEncontrado.nombre_rol;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
edicion.username = u.username || '';
|
||||||
|
edicion.nombre_completo = u.nombre_completo || '';
|
||||||
|
edicion.email = u.email || '';
|
||||||
|
edicion.id_rol = u.id_rol || rolNuevo;
|
||||||
|
edicion.password = '';
|
||||||
|
edicion.guardando = false;
|
||||||
|
edicion.mensaje = 'Actualizado.';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error(err);
|
||||||
|
edicion.guardando = false;
|
||||||
|
edicion.error = err?.error?.error || 'Error actualizando usuario.';
|
||||||
|
this.cdr.detectChanges();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,5 +6,17 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const rawApiBaseUrl = window.__SALUDUT_CONFIG__?.apiBaseUrl || '/api';
|
const getDefaultApiBaseUrl = (): string => {
|
||||||
|
const host = window.location.hostname;
|
||||||
|
const port = window.location.port;
|
||||||
|
const isLocalhost = host === 'localhost' || host === '127.0.0.1';
|
||||||
|
|
||||||
|
if (isLocalhost && port === '4200') {
|
||||||
|
return 'http://localhost:3000/api';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/api';
|
||||||
|
};
|
||||||
|
|
||||||
|
const rawApiBaseUrl = window.__SALUDUT_CONFIG__?.apiBaseUrl || getDefaultApiBaseUrl();
|
||||||
export const API_BASE_URL = rawApiBaseUrl.replace(/\/+$/, '');
|
export const API_BASE_URL = rawApiBaseUrl.replace(/\/+$/, '');
|
||||||
|
|||||||
27
saludut-inpec/src/app/interceptors/auth.interceptor.ts
Normal file
27
saludut-inpec/src/app/interceptors/auth.interceptor.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import {
|
||||||
|
HttpErrorResponse,
|
||||||
|
HttpEvent,
|
||||||
|
HttpHandler,
|
||||||
|
HttpInterceptor,
|
||||||
|
HttpRequest
|
||||||
|
} from '@angular/common/http';
|
||||||
|
import { Observable, throwError } from 'rxjs';
|
||||||
|
import { catchError } from 'rxjs/operators';
|
||||||
|
import { AuthService } from '../services/auth';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthInterceptor implements HttpInterceptor {
|
||||||
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
|
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
|
||||||
|
return next.handle(req).pipe(
|
||||||
|
catchError((error: HttpErrorResponse) => {
|
||||||
|
if (error.status === 401) {
|
||||||
|
this.authService.logout();
|
||||||
|
}
|
||||||
|
return throwError(() => error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,6 +35,14 @@ export interface RegisterRequest {
|
|||||||
sedes?: string[];
|
sedes?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActualizarUsuarioPayload {
|
||||||
|
username?: string;
|
||||||
|
password?: string;
|
||||||
|
email?: string;
|
||||||
|
nombre_completo?: string;
|
||||||
|
id_rol?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/* Interfaz Rol para la pantalla de usuarios */
|
/* Interfaz Rol para la pantalla de usuarios */
|
||||||
export interface Rol {
|
export interface Rol {
|
||||||
id_rol: number;
|
id_rol: number;
|
||||||
@ -42,6 +50,25 @@ export interface Rol {
|
|||||||
descripcion?: string | null;
|
descripcion?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface EstadisticasAutorizacionesDia {
|
||||||
|
fecha: string;
|
||||||
|
total: number;
|
||||||
|
autorizadas: number;
|
||||||
|
no_autorizadas: number;
|
||||||
|
pendientes: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EstadisticasAutorizaciones {
|
||||||
|
rango: { inicio: string; fin: string };
|
||||||
|
resumen: {
|
||||||
|
autorizadas: number;
|
||||||
|
no_autorizadas: number;
|
||||||
|
pendientes: number;
|
||||||
|
total: number;
|
||||||
|
};
|
||||||
|
dias: EstadisticasAutorizacionesDia[];
|
||||||
|
}
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
})
|
})
|
||||||
@ -55,6 +82,7 @@ export class AuthService {
|
|||||||
|
|
||||||
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||||
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
|
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
|
||||||
|
private logoutTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private http: HttpClient,
|
private http: HttpClient,
|
||||||
@ -70,6 +98,7 @@ export class AuthService {
|
|||||||
if (token && user) {
|
if (token && user) {
|
||||||
this.currentUserSubject.next(user);
|
this.currentUserSubject.next(user);
|
||||||
this.isAuthenticatedSubject.next(true);
|
this.isAuthenticatedSubject.next(true);
|
||||||
|
this.scheduleTokenExpiry(token);
|
||||||
} else {
|
} else {
|
||||||
this.clearAuth();
|
this.clearAuth();
|
||||||
}
|
}
|
||||||
@ -78,7 +107,7 @@ export class AuthService {
|
|||||||
// =========== LOGIN ===========
|
// =========== LOGIN ===========
|
||||||
|
|
||||||
login(credentials: LoginRequest): Observable<LoginResponse> {
|
login(credentials: LoginRequest): Observable<LoginResponse> {
|
||||||
return this.http.post<LoginResponse>(`${this.API_URL}/api/auth/login`, credentials).pipe(
|
return this.http.post<LoginResponse>(`${this.API_URL}/auth/login`, credentials).pipe(
|
||||||
tap(response => {
|
tap(response => {
|
||||||
this.setAuth(response.token, response.usuario);
|
this.setAuth(response.token, response.usuario);
|
||||||
}),
|
}),
|
||||||
@ -93,7 +122,7 @@ export class AuthService {
|
|||||||
|
|
||||||
register(userData: RegisterRequest): Observable<any> {
|
register(userData: RegisterRequest): Observable<any> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
return this.http.post(`${this.API_URL}/api/auth/register`, userData, { headers }).pipe(
|
return this.http.post(`${this.API_URL}/auth/register`, userData, { headers }).pipe(
|
||||||
catchError((error: any) => {
|
catchError((error: any) => {
|
||||||
console.error('Error en register:', error);
|
console.error('Error en register:', error);
|
||||||
return throwError(() => error);
|
return throwError(() => error);
|
||||||
@ -112,7 +141,7 @@ export class AuthService {
|
|||||||
|
|
||||||
verifyToken(): Observable<{ usuario: User }> {
|
verifyToken(): Observable<{ usuario: User }> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
return this.http.get<{ usuario: User }>(`${this.API_URL}/api/auth/verify`, { headers }).pipe(
|
return this.http.get<{ usuario: User }>(`${this.API_URL}/auth/verify`, { headers }).pipe(
|
||||||
tap(response => {
|
tap(response => {
|
||||||
this.updateUser(response.usuario);
|
this.updateUser(response.usuario);
|
||||||
})
|
})
|
||||||
@ -130,15 +159,54 @@ export class AuthService {
|
|||||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
||||||
this.currentUserSubject.next(user);
|
this.currentUserSubject.next(user);
|
||||||
this.isAuthenticatedSubject.next(true);
|
this.isAuthenticatedSubject.next(true);
|
||||||
|
this.scheduleTokenExpiry(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearAuth(): void {
|
private clearAuth(): void {
|
||||||
|
this.clearLogoutTimer();
|
||||||
localStorage.removeItem(this.TOKEN_KEY);
|
localStorage.removeItem(this.TOKEN_KEY);
|
||||||
localStorage.removeItem(this.USER_KEY);
|
localStorage.removeItem(this.USER_KEY);
|
||||||
this.currentUserSubject.next(null);
|
this.currentUserSubject.next(null);
|
||||||
this.isAuthenticatedSubject.next(false);
|
this.isAuthenticatedSubject.next(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private scheduleTokenExpiry(token: string): void {
|
||||||
|
const exp = this.getTokenExpiry(token);
|
||||||
|
if (!exp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const timeoutMs = exp - Date.now();
|
||||||
|
if (timeoutMs <= 0) {
|
||||||
|
this.logout();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.clearLogoutTimer();
|
||||||
|
this.logoutTimeoutId = setTimeout(() => {
|
||||||
|
this.logout();
|
||||||
|
}, timeoutMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clearLogoutTimer(): void {
|
||||||
|
if (this.logoutTimeoutId) {
|
||||||
|
clearTimeout(this.logoutTimeoutId);
|
||||||
|
this.logoutTimeoutId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getTokenExpiry(token: string): number | null {
|
||||||
|
try {
|
||||||
|
const payload = token.split('.')[1];
|
||||||
|
if (!payload) return null;
|
||||||
|
const base64 = payload.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
const padded = base64.padEnd(base64.length + (4 - (base64.length % 4)) % 4, '=');
|
||||||
|
const decoded = JSON.parse(atob(padded));
|
||||||
|
if (!decoded?.exp) return null;
|
||||||
|
return decoded.exp * 1000;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private updateUser(user: User): void {
|
private updateUser(user: User): void {
|
||||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
||||||
this.currentUserSubject.next(user);
|
this.currentUserSubject.next(user);
|
||||||
@ -183,7 +251,8 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
puedeDescargarPdfs(): boolean {
|
puedeDescargarPdfs(): boolean {
|
||||||
return this.isAdministrador();
|
const user = this.getCurrentUser();
|
||||||
|
return !!user && (user.nombre_rol === 'administrador' || user.nombre_rol === 'administrativo_sede');
|
||||||
}
|
}
|
||||||
|
|
||||||
puedeVerTodasAutorizaciones(): boolean {
|
puedeVerTodasAutorizaciones(): boolean {
|
||||||
@ -213,18 +282,27 @@ export class AuthService {
|
|||||||
getUsuarios(limit = 200, offset = 0): Observable<any[]> {
|
getUsuarios(limit = 200, offset = 0): Observable<any[]> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
const params = { limit, offset };
|
const params = { limit, offset };
|
||||||
return this.http.get<any[]>(`${this.API_URL}/api/usuarios`, { headers, params });
|
return this.http.get<any[]>(`${this.API_URL}/usuarios`, { headers, params });
|
||||||
}
|
}
|
||||||
|
|
||||||
cambiarEstadoUsuario(idUsuario: number, activo: boolean): Observable<any> {
|
cambiarEstadoUsuario(idUsuario: number, activo: boolean): Observable<any> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
return this.http.patch(
|
return this.http.patch(
|
||||||
`${this.API_URL}/api/usuarios/${idUsuario}/estado`,
|
`${this.API_URL}/usuarios/${idUsuario}/estado`,
|
||||||
{ activo },
|
{ activo },
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actualizarUsuario(idUsuario: number, payload: ActualizarUsuarioPayload): Observable<any> {
|
||||||
|
const headers = this.getAuthHeaders();
|
||||||
|
return this.http.patch(
|
||||||
|
`${this.API_URL}/usuarios/${idUsuario}`,
|
||||||
|
payload,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Alias usado en usuarios.ts
|
// Alias usado en usuarios.ts
|
||||||
actualizarEstadoUsuario(idUsuario: number, activo: boolean): Observable<any> {
|
actualizarEstadoUsuario(idUsuario: number, activo: boolean): Observable<any> {
|
||||||
return this.cambiarEstadoUsuario(idUsuario, activo);
|
return this.cambiarEstadoUsuario(idUsuario, activo);
|
||||||
@ -239,13 +317,27 @@ export class AuthService {
|
|||||||
): Observable<any[]> {
|
): Observable<any[]> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
const params = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset };
|
const params = { fecha_inicio: fechaInicio, fecha_fin: fechaFin, limit, offset };
|
||||||
return this.http.get<any[]>(`${this.API_URL}/api/autorizaciones-por-fecha`, { headers, params });
|
return this.http.get<any[]>(`${this.API_URL}/autorizaciones-por-fecha`, { headers, params });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtener roles desde el backend
|
// Obtener roles desde el backend
|
||||||
getRoles(): Observable<Rol[]> {
|
getRoles(): Observable<Rol[]> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
return this.http.get<Rol[]>(`${this.API_URL}/api/roles`, { headers });
|
return this.http.get<Rol[]>(`${this.API_URL}/roles`, { headers });
|
||||||
|
}
|
||||||
|
|
||||||
|
getEstadisticasAutorizaciones(
|
||||||
|
fechaInicio?: string,
|
||||||
|
fechaFin?: string
|
||||||
|
): Observable<EstadisticasAutorizaciones> {
|
||||||
|
const headers = this.getAuthHeaders();
|
||||||
|
const params: any = {};
|
||||||
|
if (fechaInicio) params.fecha_inicio = fechaInicio;
|
||||||
|
if (fechaFin) params.fecha_fin = fechaFin;
|
||||||
|
return this.http.get<EstadisticasAutorizaciones>(
|
||||||
|
`${this.API_URL}/autorizaciones-estadisticas`,
|
||||||
|
{ headers, params }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
crearJobPdfAutorizacion(
|
crearJobPdfAutorizacion(
|
||||||
@ -258,7 +350,7 @@ export class AuthService {
|
|||||||
payload.version = version;
|
payload.version = version;
|
||||||
}
|
}
|
||||||
return this.http.post<JobResponse>(
|
return this.http.post<JobResponse>(
|
||||||
`${this.API_URL}/api/jobs/autorizacion-pdf`,
|
`${this.API_URL}/jobs/autorizacion-pdf`,
|
||||||
payload,
|
payload,
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
@ -267,7 +359,7 @@ export class AuthService {
|
|||||||
crearJobZipAutorizaciones(fechaInicio: string, fechaFin: string): Observable<JobResponse> {
|
crearJobZipAutorizaciones(fechaInicio: string, fechaFin: string): Observable<JobResponse> {
|
||||||
const headers = this.getAuthHeaders();
|
const headers = this.getAuthHeaders();
|
||||||
return this.http.post<JobResponse>(
|
return this.http.post<JobResponse>(
|
||||||
`${this.API_URL}/api/jobs/autorizaciones-zip`,
|
`${this.API_URL}/jobs/autorizaciones-zip`,
|
||||||
{ fecha_inicio: fechaInicio, fecha_fin: fechaFin },
|
{ fecha_inicio: fechaInicio, fecha_fin: fechaFin },
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
|
|||||||
@ -4,6 +4,11 @@ export interface JobError {
|
|||||||
message?: string;
|
message?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface JobRowError {
|
||||||
|
fila?: number;
|
||||||
|
error?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface JobResult {
|
export interface JobResult {
|
||||||
ok?: boolean;
|
ok?: boolean;
|
||||||
mensaje?: string;
|
mensaje?: string;
|
||||||
@ -11,6 +16,20 @@ export interface JobResult {
|
|||||||
antiguos?: number | null;
|
antiguos?: number | null;
|
||||||
referencia?: number | null;
|
referencia?: number | null;
|
||||||
cubiertosActivos?: number | null;
|
cubiertosActivos?: number | null;
|
||||||
|
total?: number | null;
|
||||||
|
insertados?: number | null;
|
||||||
|
actualizados?: number | null;
|
||||||
|
omitidos?: number | null;
|
||||||
|
omitidas?: number | null;
|
||||||
|
desactivados?: number | null;
|
||||||
|
duplicados?: number | null;
|
||||||
|
creadas?: number | null;
|
||||||
|
sin_paciente?: number | null;
|
||||||
|
sin_cups?: number | null;
|
||||||
|
sin_ips?: number | null;
|
||||||
|
cups_no_cubiertos?: number | null;
|
||||||
|
ips_sin_convenio?: number | null;
|
||||||
|
errores?: JobRowError[];
|
||||||
downloadUrl?: string;
|
downloadUrl?: string;
|
||||||
fileName?: string;
|
fileName?: string;
|
||||||
contentType?: string;
|
contentType?: string;
|
||||||
|
|||||||
@ -19,7 +19,7 @@ export class JobsService {
|
|||||||
|
|
||||||
getJob(jobId: string): Observable<JobResponse> {
|
getJob(jobId: string): Observable<JobResponse> {
|
||||||
const headers = this.authService.getAuthHeaders();
|
const headers = this.authService.getAuthHeaders();
|
||||||
return this.http.get<JobResponse>(`${this.API_URL}/api/jobs/${jobId}`, { headers });
|
return this.http.get<JobResponse>(`${this.API_URL}/jobs/${jobId}`, { headers });
|
||||||
}
|
}
|
||||||
|
|
||||||
pollJob(jobId: string, intervalMs = 2000): Observable<JobResponse> {
|
pollJob(jobId: string, intervalMs = 2000): Observable<JobResponse> {
|
||||||
@ -34,7 +34,7 @@ export class JobsService {
|
|||||||
|
|
||||||
downloadJobFile(jobId: string): Observable<Blob> {
|
downloadJobFile(jobId: string): Observable<Blob> {
|
||||||
const headers = this.authService.getAuthHeaders();
|
const headers = this.authService.getAuthHeaders();
|
||||||
return this.http.get(`${this.API_URL}/api/jobs/${jobId}/download`, {
|
return this.http.get(`${this.API_URL}/jobs/${jobId}/download`, {
|
||||||
headers,
|
headers,
|
||||||
responseType: 'blob'
|
responseType: 'blob'
|
||||||
});
|
});
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export interface Ips {
|
|||||||
telefono?: string;
|
telefono?: string;
|
||||||
departamento: string;
|
departamento: string;
|
||||||
municipio: string;
|
municipio: string;
|
||||||
|
tiene_convenio?: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Autorizante {
|
export interface Autorizante {
|
||||||
@ -55,6 +56,12 @@ export interface CrearAutorizantePayload {
|
|||||||
activo?: boolean;
|
activo?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ActualizarAutorizantePayload {
|
||||||
|
nombre?: string;
|
||||||
|
telefono?: string | null;
|
||||||
|
cargo?: string | null;
|
||||||
|
}
|
||||||
|
|
||||||
export interface CrearAutorizacionPayload {
|
export interface CrearAutorizacionPayload {
|
||||||
interno: string;
|
interno: string;
|
||||||
id_ips: number;
|
id_ips: number;
|
||||||
@ -70,6 +77,7 @@ export interface RespuestaAutorizacion {
|
|||||||
numero_autorizacion: string;
|
numero_autorizacion: string;
|
||||||
fecha_autorizacion: string;
|
fecha_autorizacion: string;
|
||||||
version?: number;
|
version?: number;
|
||||||
|
estado_autorizacion?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AutorizacionListado {
|
export interface AutorizacionListado {
|
||||||
@ -83,11 +91,13 @@ export interface AutorizacionListado {
|
|||||||
tipo_autorizacion?: string | null;
|
tipo_autorizacion?: string | null;
|
||||||
tipo_servicio?: string | null;
|
tipo_servicio?: string | null;
|
||||||
version?: number | null;
|
version?: number | null;
|
||||||
|
estado_autorizacion?: string | null;
|
||||||
id_ips?: number | null;
|
id_ips?: number | null;
|
||||||
numero_documento_autorizante?: number | null;
|
numero_documento_autorizante?: number | null;
|
||||||
nombre_ips: string;
|
nombre_ips: string;
|
||||||
municipio: string;
|
municipio?: string | null;
|
||||||
departamento: string;
|
departamento?: string | null;
|
||||||
|
ips_tiene_convenio?: boolean | null;
|
||||||
nombre_autorizante: string;
|
nombre_autorizante: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,6 +112,14 @@ export interface AutorizacionVersionResponse {
|
|||||||
versiones: AutorizacionVersion[];
|
versiones: AutorizacionVersion[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface CupInfo {
|
||||||
|
codigo: string;
|
||||||
|
descripcion: string;
|
||||||
|
nivel?: string | null;
|
||||||
|
especialidad?: string | null;
|
||||||
|
cubierto: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
// ====== Servicio ======
|
// ====== Servicio ======
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -124,7 +142,7 @@ export class PacienteService {
|
|||||||
|
|
||||||
buscarPorDocumento(numero_documento: string): Observable<Paciente[]> {
|
buscarPorDocumento(numero_documento: string): Observable<Paciente[]> {
|
||||||
const params = new HttpParams().set('numero_documento', numero_documento);
|
const params = new HttpParams().set('numero_documento', numero_documento);
|
||||||
return this.http.get<Paciente[]>(`${this.API_URL}/api/pacientes`, {
|
return this.http.get<Paciente[]>(`${this.API_URL}/pacientes`, {
|
||||||
params,
|
params,
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
@ -132,7 +150,7 @@ export class PacienteService {
|
|||||||
|
|
||||||
buscarPorInterno(interno: string): Observable<Paciente[]> {
|
buscarPorInterno(interno: string): Observable<Paciente[]> {
|
||||||
const params = new HttpParams().set('interno', interno);
|
const params = new HttpParams().set('interno', interno);
|
||||||
return this.http.get<Paciente[]>(`${this.API_URL}/api/pacientes`, {
|
return this.http.get<Paciente[]>(`${this.API_URL}/pacientes`, {
|
||||||
params,
|
params,
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
@ -140,7 +158,7 @@ export class PacienteService {
|
|||||||
|
|
||||||
buscarPorNombre(nombre: string): Observable<Paciente[]> {
|
buscarPorNombre(nombre: string): Observable<Paciente[]> {
|
||||||
const params = new HttpParams().set('nombre', nombre);
|
const params = new HttpParams().set('nombre', nombre);
|
||||||
return this.http.get<Paciente[]>(`${this.API_URL}/api/pacientes`, {
|
return this.http.get<Paciente[]>(`${this.API_URL}/pacientes`, {
|
||||||
params,
|
params,
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
@ -153,37 +171,48 @@ export class PacienteService {
|
|||||||
if (verTodas) {
|
if (verTodas) {
|
||||||
params = params.set('ver_todas', '1');
|
params = params.set('ver_todas', '1');
|
||||||
}
|
}
|
||||||
return this.http.get<Ips[]>(`${this.API_URL}/api/ips-por-interno`, {
|
return this.http.get<Ips[]>(`${this.API_URL}/ips-por-interno`, {
|
||||||
params,
|
params,
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
obtenerAutorizantes(): Observable<Autorizante[]> {
|
obtenerAutorizantes(): Observable<Autorizante[]> {
|
||||||
return this.http.get<Autorizante[]>(`${this.API_URL}/api/autorizantes`, {
|
return this.http.get<Autorizante[]>(`${this.API_URL}/autorizantes`, {
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
obtenerAutorizantesAdmin(): Observable<Autorizante[]> {
|
obtenerAutorizantesAdmin(): Observable<Autorizante[]> {
|
||||||
return this.http.get<Autorizante[]>(`${this.API_URL}/api/autorizantes/admin`, {
|
return this.http.get<Autorizante[]>(`${this.API_URL}/autorizantes/admin`, {
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
actualizarEstadoAutorizante(numeroDocumento: number, activo: boolean): Observable<any> {
|
actualizarEstadoAutorizante(numeroDocumento: number, activo: boolean): Observable<any> {
|
||||||
return this.http.patch(
|
return this.http.patch(
|
||||||
`${this.API_URL}/api/autorizantes/${numeroDocumento}/estado`,
|
`${this.API_URL}/autorizantes/${numeroDocumento}/estado`,
|
||||||
{ activo },
|
{ activo },
|
||||||
{ headers: this.getAuthHeaders() }
|
{ headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actualizarDatosAutorizante(
|
||||||
|
numeroDocumento: number,
|
||||||
|
payload: ActualizarAutorizantePayload
|
||||||
|
): Observable<any> {
|
||||||
|
return this.http.patch(
|
||||||
|
`${this.API_URL}/autorizantes/${numeroDocumento}`,
|
||||||
|
payload,
|
||||||
|
{ headers: this.getAuthHeaders() }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Autorizaciones ----
|
// ---- Autorizaciones ----
|
||||||
|
|
||||||
crearAutorizacion(payload: CrearAutorizacionPayload): Observable<RespuestaAutorizacion> {
|
crearAutorizacion(payload: CrearAutorizacionPayload): Observable<RespuestaAutorizacion> {
|
||||||
return this.http.post<RespuestaAutorizacion>(
|
return this.http.post<RespuestaAutorizacion>(
|
||||||
`${this.API_URL}/api/autorizaciones`,
|
`${this.API_URL}/autorizaciones`,
|
||||||
payload,
|
payload,
|
||||||
{ headers: this.getAuthHeaders() }
|
{ headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
@ -194,7 +223,7 @@ export class PacienteService {
|
|||||||
payload: CrearAutorizacionPayload
|
payload: CrearAutorizacionPayload
|
||||||
): Observable<RespuestaAutorizacion> {
|
): Observable<RespuestaAutorizacion> {
|
||||||
return this.http.put<RespuestaAutorizacion>(
|
return this.http.put<RespuestaAutorizacion>(
|
||||||
`${this.API_URL}/api/autorizaciones/${numeroAutorizacion}`,
|
`${this.API_URL}/autorizaciones/${numeroAutorizacion}`,
|
||||||
payload,
|
payload,
|
||||||
{ headers: this.getAuthHeaders() }
|
{ headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
@ -202,7 +231,7 @@ export class PacienteService {
|
|||||||
|
|
||||||
obtenerVersionesAutorizacion(numeroAutorizacion: string): Observable<AutorizacionVersionResponse> {
|
obtenerVersionesAutorizacion(numeroAutorizacion: string): Observable<AutorizacionVersionResponse> {
|
||||||
return this.http.get<AutorizacionVersionResponse>(
|
return this.http.get<AutorizacionVersionResponse>(
|
||||||
`${this.API_URL}/api/autorizaciones/${numeroAutorizacion}/versiones`,
|
`${this.API_URL}/autorizaciones/${numeroAutorizacion}/versiones`,
|
||||||
{ headers: this.getAuthHeaders() }
|
{ headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -210,7 +239,7 @@ export class PacienteService {
|
|||||||
obtenerAutorizacionesPorInterno(interno: string): Observable<AutorizacionListado[]> {
|
obtenerAutorizacionesPorInterno(interno: string): Observable<AutorizacionListado[]> {
|
||||||
const params = new HttpParams().set('interno', interno);
|
const params = new HttpParams().set('interno', interno);
|
||||||
return this.http.get<AutorizacionListado[]>(
|
return this.http.get<AutorizacionListado[]>(
|
||||||
`${this.API_URL}/api/autorizaciones`,
|
`${this.API_URL}/autorizaciones`,
|
||||||
{ params, headers: this.getAuthHeaders() }
|
{ params, headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -229,7 +258,7 @@ export class PacienteService {
|
|||||||
|
|
||||||
// 3. Hacemos la petición con estos headers limpios.
|
// 3. Hacemos la petición con estos headers limpios.
|
||||||
return this.http.post<JobResponse>(
|
return this.http.post<JobResponse>(
|
||||||
`${this.API_URL}/api/cargar-excel-pacientes`,
|
`${this.API_URL}/cargar-excel-pacientes`,
|
||||||
formData,
|
formData,
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
@ -242,7 +271,46 @@ export class PacienteService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return this.http.post<JobResponse>(
|
return this.http.post<JobResponse>(
|
||||||
`${this.API_URL}/api/cargar-cups`,
|
`${this.API_URL}/cargar-cups`,
|
||||||
|
formData,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarIps(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-ips`,
|
||||||
|
formData,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarReps(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-reps`,
|
||||||
|
formData,
|
||||||
|
{ headers }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
cargarAutorizacionesMasivas(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-autorizaciones-masivas`,
|
||||||
formData,
|
formData,
|
||||||
{ headers }
|
{ headers }
|
||||||
);
|
);
|
||||||
@ -250,18 +318,49 @@ export class PacienteService {
|
|||||||
|
|
||||||
crearAutorizante(payload: CrearAutorizantePayload): Observable<any> {
|
crearAutorizante(payload: CrearAutorizantePayload): Observable<any> {
|
||||||
return this.http.post(
|
return this.http.post(
|
||||||
`${this.API_URL}/api/autorizantes`,
|
`${this.API_URL}/autorizantes`,
|
||||||
payload,
|
payload,
|
||||||
{ headers: this.getAuthHeaders() }
|
{ headers: this.getAuthHeaders() }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
buscarCupsCubiertos(termino: string): Observable<any[]> {
|
buscarCups(termino: string, limit = 200): Observable<CupInfo[]> {
|
||||||
const params = new HttpParams().set('q', termino);
|
const params = new HttpParams()
|
||||||
return this.http.get<any[]>(`${this.API_URL}/api/cups-cubiertos`, {
|
.set('q', termino)
|
||||||
|
.set('limit', String(limit));
|
||||||
|
return this.http.get<CupInfo[]>(`${this.API_URL}/cups`, {
|
||||||
params,
|
params,
|
||||||
headers: this.getAuthHeaders()
|
headers: this.getAuthHeaders()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
actualizarEstadoAutorizacion(
|
||||||
|
numeroAutorizacion: string,
|
||||||
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado'
|
||||||
|
): Observable<any> {
|
||||||
|
return this.http.patch(
|
||||||
|
`${this.API_URL}/autorizaciones/${numeroAutorizacion}/estado`,
|
||||||
|
{ estado_autorizacion: estado },
|
||||||
|
{ headers: this.getAuthHeaders() }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
actualizarEstadoAutorizacionesMasivo(
|
||||||
|
fechaInicio: string,
|
||||||
|
fechaFin: string,
|
||||||
|
estado: 'pendiente' | 'autorizado' | 'no_autorizado',
|
||||||
|
soloPendientes = false
|
||||||
|
): Observable<any> {
|
||||||
|
return this.http.patch(
|
||||||
|
`${this.API_URL}/autorizaciones/estado-masivo`,
|
||||||
|
{
|
||||||
|
fecha_inicio: fechaInicio,
|
||||||
|
fecha_fin: fechaFin,
|
||||||
|
estado_autorizacion: estado,
|
||||||
|
solo_pendientes: soloPendientes,
|
||||||
|
},
|
||||||
|
{ headers: this.getAuthHeaders() }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,9 @@ import { AppComponent } from './app/app';
|
|||||||
import { provideRouter } from '@angular/router';
|
import { provideRouter } from '@angular/router';
|
||||||
import { routes } from './app/app.routes';
|
import { routes } from './app/app.routes';
|
||||||
import { importProvidersFrom } from '@angular/core';
|
import { importProvidersFrom } from '@angular/core';
|
||||||
import { HttpClientModule } from '@angular/common/http';
|
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
|
||||||
|
import { AuthInterceptor } from './app/interceptors/auth.interceptor';
|
||||||
|
|
||||||
bootstrapApplication(AppComponent, {
|
bootstrapApplication(AppComponent, {
|
||||||
providers: [
|
providers: [
|
||||||
@ -15,6 +16,11 @@ bootstrapApplication(AppComponent, {
|
|||||||
HttpClientModule,
|
HttpClientModule,
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule
|
ReactiveFormsModule
|
||||||
)
|
),
|
||||||
|
{
|
||||||
|
provide: HTTP_INTERCEPTORS,
|
||||||
|
useClass: AuthInterceptor,
|
||||||
|
multi: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@ -13,12 +13,23 @@
|
|||||||
--color-bg: #f5f5f5;
|
--color-bg: #f5f5f5;
|
||||||
--color-card: #ffffff;
|
--color-card: #ffffff;
|
||||||
--color-border: #e0e0e0;
|
--color-border: #e0e0e0;
|
||||||
|
--color-surface: #f8fafc;
|
||||||
|
--color-surface-alt: #f1f5f9;
|
||||||
|
--color-surface-muted: #eef2f7;
|
||||||
|
--color-surface-strong: #e7eef7;
|
||||||
|
|
||||||
--color-text-main: #222222;
|
--color-text-main: #222222;
|
||||||
--color-text-muted: #666666;
|
--color-text-muted: #666666;
|
||||||
|
|
||||||
--color-success: #2e7d32;
|
--color-success: #2e7d32;
|
||||||
--color-error: #c62828;
|
--color-error: #c62828;
|
||||||
|
--color-permission-yes-bg: #f0f9f0;
|
||||||
|
--color-permission-yes-border: #dcfce7;
|
||||||
|
--color-permission-no-bg: #fff5f5;
|
||||||
|
--color-permission-no-border: #fed7d7;
|
||||||
|
|
||||||
|
--shadow-card: 0 4px 12px rgba(0, 0, 0, 0.06);
|
||||||
|
--shadow-float: 0 10px 24px rgba(0, 0, 0, 0.12);
|
||||||
|
|
||||||
--color-table-head: #f2f2f2;
|
--color-table-head: #f2f2f2;
|
||||||
--color-table-row: #ffffff;
|
--color-table-row: #ffffff;
|
||||||
@ -45,25 +56,33 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
--color-primary: #4fa3ff;
|
--color-primary: #3c7fc1;
|
||||||
--color-primary-dark: #2d7dcc;
|
--color-primary-dark: #2f628f;
|
||||||
--color-primary-soft: #1d2b3b;
|
--color-primary-soft: #1b2a3b;
|
||||||
--color-header-grad-2: #1f4b87;
|
--color-header-grad-2: #1e3f66;
|
||||||
|
|
||||||
--color-bg: #0f141a;
|
--color-bg: #0f141a;
|
||||||
--color-card: #151c24;
|
--color-card: #121a24;
|
||||||
--color-border: #2a3440;
|
--color-border: #1f2a36;
|
||||||
|
--color-surface: #0f1621;
|
||||||
|
--color-surface-alt: #151e2a;
|
||||||
|
--color-surface-muted: #1b2635;
|
||||||
|
--color-surface-strong: #1f2c3d;
|
||||||
|
|
||||||
--color-text-main: #e7edf5;
|
--color-text-main: #e7edf5;
|
||||||
--color-text-muted: #a8b3c2;
|
--color-text-muted: #a1adbd;
|
||||||
|
|
||||||
--color-success: #3bb273;
|
--color-success: #3bb273;
|
||||||
--color-error: #ff6b6b;
|
--color-error: #ff6b6b;
|
||||||
|
--color-permission-yes-bg: rgba(46, 125, 50, 0.18);
|
||||||
|
--color-permission-yes-border: rgba(46, 125, 50, 0.4);
|
||||||
|
--color-permission-no-bg: rgba(198, 40, 40, 0.16);
|
||||||
|
--color-permission-no-border: rgba(198, 40, 40, 0.45);
|
||||||
|
|
||||||
--color-table-head: #1f2a36;
|
--color-table-head: #1b2430;
|
||||||
--color-table-row: #151c24;
|
--color-table-row: #121a24;
|
||||||
--color-table-row-alt: #19222c;
|
--color-table-row-alt: #16202b;
|
||||||
--color-table-hover: #223046;
|
--color-table-hover: #1f2a36;
|
||||||
|
|
||||||
--color-input-bg: #111821;
|
--color-input-bg: #111821;
|
||||||
--color-input-border: #2c3a4a;
|
--color-input-border: #2c3a4a;
|
||||||
@ -77,6 +96,9 @@
|
|||||||
--color-cup-item-bg: #151c24;
|
--color-cup-item-bg: #151c24;
|
||||||
--color-cup-item-border: #273241;
|
--color-cup-item-border: #273241;
|
||||||
--color-cup-item-hover: #4fa3ff;
|
--color-cup-item-hover: #4fa3ff;
|
||||||
|
|
||||||
|
--shadow-card: 0 6px 18px rgba(0, 0, 0, 0.5);
|
||||||
|
--shadow-float: 0 16px 32px rgba(0, 0, 0, 0.55);
|
||||||
}
|
}
|
||||||
|
|
||||||
*,
|
*,
|
||||||
@ -127,7 +149,8 @@ textarea {
|
|||||||
background: var(--color-card);
|
background: var(--color-card);
|
||||||
border-radius: var(--radius-card);
|
border-radius: var(--radius-card);
|
||||||
padding: 20px 24px;
|
padding: 20px 24px;
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.06);
|
border: 1px solid var(--color-border);
|
||||||
|
box-shadow: var(--shadow-card);
|
||||||
}
|
}
|
||||||
|
|
||||||
.card.compact {
|
.card.compact {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user