cambios finales
This commit is contained in:
parent
d611cec040
commit
5844cfe523
@ -1,47 +1,47 @@
|
||||
-- INSERTS 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 ('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 ('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 ('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 ('130', 'CPOMS ACACIAS', 'ACACIAS', 'META', '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');
|
||||
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 ('150', 'CPAMS EL BARNE', 'COMBITA', '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');
|
||||
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 ('107', 'EPMSC GUATEQUE', 'GUATEQUE', 'BOYACA', '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');
|
||||
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 ('124', 'CPMS LA MESA', 'LA MESA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('138', 'CPMS GIRARDOT', 'GIRARDOT', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('116', 'EPMSC CAQUEZA', 'CAQUEZA', 'CUNDINAMARCA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('139', 'CPMS NEIVA', 'NEIVA', 'HUILA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('158', 'CPMS GUAMO', 'GUAMO', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('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 ('104', 'CPMS CHIQUINQUIRA', 'CHIQUINQUIRA', 'BOYACA', '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');
|
||||
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 ('144', 'EPMSC CHAPARRAL', 'CHAPARRAL', 'TOLIMA', 'CENTRAL', 'CENTRAL');
|
||||
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 ('126', 'CPMS UBATE', 'UBATE', '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');
|
||||
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 ('110', 'CPMS RAMIRIQUI', 'RAMIRIQUI', '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');
|
||||
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 ('101', 'EPMSC LETICIA', 'LETICIA', 'AMAZONAS', 'CENTRAL', 'CENTRAL');
|
||||
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 ('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 ('9001', 'CPMMSF FACATATIVA', 'FACATATIVA', 'CUNDINAMARCA', 'POLICIA', 'CENTRAL');
|
||||
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 ('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 ('9016', 'CPAMSEJECO', 'FACATATIVA', 'CUNDINAMARCA', 'EJERCITO', 'CENTRAL');
|
||||
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 ('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 ('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 ('9033', 'CPMMS FUERZA AEREA', 'VILLAVICENCIO', 'META', 'FUERZA AEREA', 'CENTRAL');
|
||||
-- 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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
|
||||
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 ('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;
|
||||
|
||||
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
|
||||
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 ('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 ('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;
|
||||
|
||||
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,
|
||||
telefono text,
|
||||
departamento text,
|
||||
municipio text
|
||||
municipio text,
|
||||
tiene_convenio boolean DEFAULT true
|
||||
);
|
||||
|
||||
-- ===== Ingreso =====
|
||||
@ -91,7 +92,8 @@ CREATE TABLE IF NOT EXISTS usuario (
|
||||
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ultimo_login TIMESTAMP,
|
||||
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 (
|
||||
@ -163,9 +165,23 @@ CREATE TABLE IF NOT EXISTS autorizacion (
|
||||
cup_codigo varchar(20),
|
||||
tipo_autorizacion varchar(50) NOT NULL DEFAULT 'consultas_externas',
|
||||
tipo_servicio varchar(50),
|
||||
estado_autorizacion varchar(20) NOT NULL DEFAULT 'pendiente',
|
||||
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)
|
||||
VALUES (1, COALESCE((SELECT MAX(numero_autorizacion) FROM autorizacion), 'UTUSCPGB00'))
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
@ -201,6 +217,19 @@ CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||
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
|
||||
ON autorizacion_version (numero_autorizacion, version);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -55,6 +55,9 @@
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular/build:dev-server",
|
||||
"options": {
|
||||
"proxyConfig": "proxy.conf.json"
|
||||
},
|
||||
"configurations": {
|
||||
"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 { UsuariosComponent } from './components/usuarios/usuarios';
|
||||
import { CargarCupsComponent } from './components/cargar-cups/cargar-cups';
|
||||
import { EstadisticasAutorizacionesComponent } from './components/estadisticas-autorizaciones/estadisticas-autorizaciones';
|
||||
import { CargarAutorizacionesMasivasComponent } from './components/cargar-autorizaciones-masivas/cargar-autorizaciones-masivas';
|
||||
import { CargarIpsRepsComponent } from './components/cargar-ips-reps/cargar-ips-reps';
|
||||
import { CargarPacientesComponent } from './components/cargar-pacientes/cargar-pacientes';
|
||||
|
||||
|
||||
export const routes: Routes = [
|
||||
@ -30,6 +34,12 @@ export const routes: Routes = [
|
||||
{
|
||||
path: 'autorizaciones-por-fecha',
|
||||
component: AutorizacionesPorFechaComponent,
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
|
||||
{
|
||||
path: 'estadisticas-autorizaciones',
|
||||
component: EstadisticasAutorizacionesComponent,
|
||||
canActivate: [AuthGuard, AdminGuard],
|
||||
},
|
||||
|
||||
@ -45,6 +55,24 @@ export const routes: Routes = [
|
||||
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
|
||||
{ path: '**', redirectTo: 'dashboard' },
|
||||
|
||||
|
||||
@ -12,15 +12,15 @@
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
background: #fef2f2;
|
||||
border-left: 4px solid #dc2626;
|
||||
color: #dc2626;
|
||||
background: var(--color-permission-no-bg);
|
||||
border-left: 4px solid var(--color-error);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background: #f0fdf4;
|
||||
border-left: 4px solid #16a34a;
|
||||
color: #16a34a;
|
||||
background: var(--color-permission-yes-bg);
|
||||
border-left: 4px solid var(--color-success);
|
||||
color: var(--color-success);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
@ -62,7 +62,7 @@
|
||||
|
||||
.filtros-card h2 {
|
||||
margin: 0 0 20px 0;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -83,18 +83,19 @@
|
||||
display: block;
|
||||
margin-bottom: 6px;
|
||||
font-weight: 600;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 2px solid #e5e7eb;
|
||||
border: 2px solid var(--color-input-border);
|
||||
border-radius: 8px;
|
||||
font-size: 0.95rem;
|
||||
transition: all 0.2s ease;
|
||||
background-color: #ffffff;
|
||||
background-color: var(--color-input-bg);
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
@ -104,12 +105,12 @@
|
||||
}
|
||||
|
||||
.form-group input.error {
|
||||
border-color: #dc2626;
|
||||
background-color: #fef2f2;
|
||||
border-color: var(--color-error);
|
||||
background-color: var(--color-permission-no-bg);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc2626;
|
||||
color: var(--color-error);
|
||||
font-size: 0.8rem;
|
||||
margin-top: 4px;
|
||||
font-weight: 500;
|
||||
@ -167,7 +168,7 @@
|
||||
|
||||
.resultados-header h2 {
|
||||
margin: 0;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
@ -175,13 +176,75 @@
|
||||
.resultados-actions {
|
||||
display: flex;
|
||||
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-container {
|
||||
overflow-x: auto;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.autorizaciones-table {
|
||||
@ -197,23 +260,23 @@
|
||||
}
|
||||
|
||||
.autorizaciones-table tr:hover td {
|
||||
background: #f8fafc;
|
||||
background: var(--color-table-hover);
|
||||
}
|
||||
|
||||
.autorizaciones-table tr.even-row td {
|
||||
background: #fafbfc;
|
||||
background: var(--color-table-row-alt);
|
||||
}
|
||||
|
||||
.numero-autorizacion {
|
||||
font-weight: 600;
|
||||
color: #1976d2;
|
||||
color: var(--color-primary);
|
||||
font-family: "Courier New", monospace;
|
||||
}
|
||||
|
||||
.fecha {
|
||||
white-space: nowrap;
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.interno {
|
||||
@ -227,6 +290,29 @@
|
||||
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 {
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
@ -235,15 +321,58 @@
|
||||
.nombre-paciente,
|
||||
.ips,
|
||||
.autorizante,
|
||||
.establecimiento {
|
||||
.establecimiento,
|
||||
.estado {
|
||||
max-width: 180px;
|
||||
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,
|
||||
.departamento {
|
||||
font-size: 0.85rem;
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.acciones {
|
||||
@ -251,6 +380,13 @@
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.no-result {
|
||||
text-align: center;
|
||||
padding: 24px 16px;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.btn-descargar:hover:not(:disabled) {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
@ -269,14 +405,14 @@
|
||||
|
||||
.empty-state h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.3rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
margin: 0;
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@ -338,10 +474,24 @@
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.resultados-filtro-numero {
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.resultados-filtro-numero input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.resultados-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.estado-masivo {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.autorizaciones-table th,
|
||||
.autorizaciones-table td {
|
||||
padding: 8px 12px;
|
||||
@ -350,7 +500,8 @@
|
||||
.nombre-paciente,
|
||||
.ips,
|
||||
.autorizante,
|
||||
.establecimiento {
|
||||
.establecimiento,
|
||||
.estado {
|
||||
max-width: 120px;
|
||||
}
|
||||
}
|
||||
@ -361,10 +512,23 @@
|
||||
}
|
||||
|
||||
.btn-exportar,
|
||||
.btn-descargar-todos {
|
||||
.btn-descargar-todos,
|
||||
.estado-masivo,
|
||||
.estado-masivo button {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.estado-masivo {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.estado-masivo select,
|
||||
.estado-masivo button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
padding: 40px 16px;
|
||||
}
|
||||
|
||||
@ -3,10 +3,15 @@
|
||||
<app-header
|
||||
title="Autorizaciones por fecha"
|
||||
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"
|
||||
backLabel="Volver"
|
||||
(back)="volverAtras()"
|
||||
[showLogo]="false"
|
||||
></app-header>
|
||||
|
||||
<!-- Mensajes -->
|
||||
@ -75,8 +80,44 @@
|
||||
<!-- Resultados -->
|
||||
<div class="resultados-section card" *ngIf="hayResultados || autorizaciones.length > 0">
|
||||
<div class="resultados-header">
|
||||
<h2>Resultados ({{ autorizaciones.length }} autorizaciones)</h2>
|
||||
<div class="resultados-actions">
|
||||
<h2>
|
||||
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
|
||||
class="btn btn-secondary btn-exportar"
|
||||
(click)="exportarAExcel()"
|
||||
@ -85,14 +126,14 @@
|
||||
>
|
||||
Exportar Excel
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-descargar-todos"
|
||||
(click)="descargarTodosLosPdfs()"
|
||||
title="Descargar todos los PDFs"
|
||||
[disabled]="descargandoZip"
|
||||
>
|
||||
Descargar todos los PDFs
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-secondary btn-descargar-todos"
|
||||
(click)="descargarTodosLosPdfs()"
|
||||
title="Descargar todos los PDFs"
|
||||
[disabled]="descargandoZip"
|
||||
>
|
||||
Descargar todos los PDFs
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -109,18 +150,25 @@
|
||||
<th>Tipo autorizacion</th>
|
||||
<th>Tipo servicio</th>
|
||||
<th>CUPS</th>
|
||||
<th>Cubre</th>
|
||||
<th>Nivel</th>
|
||||
<th>IPS</th>
|
||||
<th>Municipio</th>
|
||||
<th>Departamento</th>
|
||||
<th>Autorizante</th>
|
||||
<th>Establecimiento</th>
|
||||
<th>Acciones</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngIf="autorizacionesFiltradas.length === 0">
|
||||
<td class="no-result" colspan="17">
|
||||
No hay autorizaciones para ese numero.
|
||||
</td>
|
||||
</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"
|
||||
>
|
||||
<td class="numero-autorizacion">
|
||||
@ -137,18 +185,61 @@
|
||||
{{ getTipoServicioLabel(aut.tipo_servicio) }}
|
||||
</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="ips">{{ aut.nombre_ips }}</td>
|
||||
<td class="municipio">{{ aut.municipio }}</td>
|
||||
<td class="departamento">{{ aut.departamento }}</td>
|
||||
<td class="ips">
|
||||
<div class="ips-info">
|
||||
<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="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">
|
||||
<button
|
||||
class="btn btn-success btn-sm btn-descargar"
|
||||
(click)="descargarPdf(aut.numero_autorizacion)"
|
||||
(click)="descargarPdf(aut)"
|
||||
title="Descargar PDF"
|
||||
[disabled]="descargandoPdf"
|
||||
[disabled]="descargandoPdf || !puedeDescargarPdfAutorizacion(aut)"
|
||||
>
|
||||
PDF
|
||||
</button>
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
import { ChangeDetectorRef, Component, OnInit, Inject } from '@angular/core';
|
||||
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 { AuthService } from '../../services/auth';
|
||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||
import { JobsService } from '../../services/jobs';
|
||||
import { PacienteService } from '../../services/paciente';
|
||||
|
||||
@Component({
|
||||
selector: 'app-autorizaciones-por-fecha',
|
||||
standalone: true,
|
||||
imports: [CommonModule, ReactiveFormsModule, AppHeaderComponent],
|
||||
imports: [CommonModule, ReactiveFormsModule, FormsModule, AppHeaderComponent],
|
||||
templateUrl: './autorizaciones-por-fecha.html',
|
||||
styleUrls: ['./autorizaciones-por-fecha.css']
|
||||
})
|
||||
@ -23,6 +24,12 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
avisoAutorizaciones = false;
|
||||
descargandoZip = 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
|
||||
hayResultados = false;
|
||||
@ -35,6 +42,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
private fb: FormBuilder,
|
||||
private authService: AuthService,
|
||||
private jobsService: JobsService,
|
||||
private pacienteService: PacienteService,
|
||||
private cdr: ChangeDetectorRef,
|
||||
@Inject(DOCUMENT) private document: Document
|
||||
) {
|
||||
@ -45,11 +53,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
// Solo admin puede entrar (además del guard)
|
||||
if (!this.authService.isAdministrador()) {
|
||||
this.errorMessage = 'No tienes permisos para acceder a esta página.';
|
||||
return;
|
||||
}
|
||||
this.esAdmin = this.authService.isAdministrador();
|
||||
|
||||
// Rango por defecto: últimos 30 días
|
||||
const hoy = new Date();
|
||||
@ -84,6 +88,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
|
||||
this.isLoading = true;
|
||||
this.autorizaciones = [];
|
||||
this.autorizacionesFiltradas = [];
|
||||
this.hayResultados = false;
|
||||
|
||||
this.authService
|
||||
@ -100,6 +105,7 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
.subscribe({
|
||||
next: (data) => {
|
||||
this.autorizaciones = data || [];
|
||||
this.aplicarFiltroNumero();
|
||||
this.hayResultados = this.autorizaciones.length > 0;
|
||||
this.avisoAutorizaciones =
|
||||
this.autorizaciones.length >= this.limiteAutorizaciones;
|
||||
@ -117,8 +123,19 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
}
|
||||
|
||||
// ========= PDF INDIVIDUAL =========
|
||||
descargarPdf(numeroAutorizacion: string): void {
|
||||
descargarPdf(autorizacion: any): void {
|
||||
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.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 =========
|
||||
descargarTodosLosPdfs(): void {
|
||||
this.limpiarMensajes();
|
||||
|
||||
if (!this.esAdmin) {
|
||||
this.errorMessage = 'No tienes permisos para descargar todos los PDFs.';
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.autorizaciones || this.autorizaciones.length === 0) {
|
||||
this.errorMessage = 'No hay autorizaciones para descargar.';
|
||||
return;
|
||||
@ -302,6 +439,50 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
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 {
|
||||
this.errorMessage = null;
|
||||
this.successMessage = null;
|
||||
@ -347,7 +528,14 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
|
||||
// ========= EXPORTAR A CSV SIMPLE =========
|
||||
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.';
|
||||
return;
|
||||
}
|
||||
@ -366,13 +554,14 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
'Municipio',
|
||||
'Departamento',
|
||||
'Autorizante',
|
||||
'Establecimiento'
|
||||
'Establecimiento',
|
||||
'Estado'
|
||||
];
|
||||
|
||||
const separator = ';';
|
||||
const csvContent = [
|
||||
headers.map((header) => this.csvValue(header)).join(separator),
|
||||
...this.autorizaciones.map((aut) =>
|
||||
...dataset.map((aut) =>
|
||||
[
|
||||
this.csvValue(aut.numero_autorizacion),
|
||||
this.csvValue(aut.version || ''),
|
||||
@ -387,7 +576,8 @@ export class AutorizacionesPorFechaComponent implements OnInit {
|
||||
this.csvValue(aut.municipio),
|
||||
this.csvValue(aut.departamento),
|
||||
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('\n');
|
||||
|
||||
@ -121,6 +121,22 @@
|
||||
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 {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
@ -244,12 +260,39 @@
|
||||
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 {
|
||||
color: var(--color-text-muted);
|
||||
font-size: var(--font-size-sm);
|
||||
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 === */
|
||||
.aut-modal-backdrop {
|
||||
position: fixed;
|
||||
|
||||
@ -9,6 +9,9 @@
|
||||
[userRole]="getCurrentUser()?.nombre_rol"
|
||||
[showLogout]="isLoggedIn()"
|
||||
(logout)="logout()"
|
||||
[showBack]="true"
|
||||
backLabel="Volver"
|
||||
(back)="irADashboard()"
|
||||
></app-header>
|
||||
|
||||
<!-- Tarjeta de búsqueda -->
|
||||
@ -36,31 +39,8 @@
|
||||
</button>
|
||||
</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()">
|
||||
<button
|
||||
class="btn btn-gradient"
|
||||
@ -69,15 +49,12 @@
|
||||
>
|
||||
Ver autorizaciones por fecha
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nav-actions">
|
||||
<button
|
||||
class="btn btn-secondary"
|
||||
(click)="irADashboard()"
|
||||
title="Volver al dashboard"
|
||||
(click)="irACargarPacientes()"
|
||||
title="Cargar pacientes"
|
||||
>
|
||||
Volver al dashboard
|
||||
Cargar pacientes
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@ -205,7 +182,7 @@
|
||||
>
|
||||
<option value="">-- Seleccione IPS --</option>
|
||||
<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>
|
||||
</select>
|
||||
<button
|
||||
@ -247,6 +224,7 @@
|
||||
[(ngModel)]="formAutorizacion.cup_codigo"
|
||||
placeholder="Codigo o descripcion"
|
||||
(keyup.enter)="buscarCups()"
|
||||
(input)="onCupInputChange()"
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
@ -275,10 +253,34 @@
|
||||
<div class="cup-code">{{ cup.codigo }}</div>
|
||||
<div class="cup-desc">{{ cup.descripcion }}</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>
|
||||
</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">
|
||||
{{ errorCups }}
|
||||
</div>
|
||||
@ -357,6 +359,7 @@
|
||||
<th>Version</th>
|
||||
<th>IPS</th>
|
||||
<th>Autoriza</th>
|
||||
<th>Estado</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@ -393,8 +396,50 @@
|
||||
</select>
|
||||
</div>
|
||||
</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 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>
|
||||
<div class="accion-buttons">
|
||||
<button
|
||||
@ -405,15 +450,15 @@
|
||||
Editar
|
||||
</button>
|
||||
<button
|
||||
*ngIf="puedeDescargarPdfs()"
|
||||
*ngIf="puedeDescargarPdfAutorizacion(a)"
|
||||
class="btn btn-secondary btn-sm"
|
||||
[disabled]="descargandoPdf"
|
||||
(click)="descargarPdf(a.numero_autorizacion, getVersionSeleccionada(a.numero_autorizacion, a.version))"
|
||||
(click)="descargarPdf(a, getVersionSeleccionada(a.numero_autorizacion, a.version))"
|
||||
>
|
||||
PDF
|
||||
</button>
|
||||
<span *ngIf="!puedeDescargarPdfs()" class="pdf-restricted">
|
||||
Solo admin
|
||||
<span *ngIf="!puedeDescargarPdfAutorizacion(a)" class="pdf-restricted">
|
||||
Pendiente de aprobacion
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@ -3,7 +3,7 @@ import { Router } from '@angular/router';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { AuthService } from '../../services/auth';
|
||||
import { PacienteService, AutorizacionVersion } from '../../services/paciente';
|
||||
import { PacienteService, AutorizacionVersion, CupInfo } from '../../services/paciente';
|
||||
import { finalize } from 'rxjs/operators';
|
||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||
import { JobsService } from '../../services/jobs';
|
||||
@ -26,21 +26,19 @@ export class AutorizacionesComponent {
|
||||
cargando = false;
|
||||
error: string | null = null;
|
||||
|
||||
// ---- Excel ----
|
||||
cargandoExcel = false;
|
||||
estadoCargaExcel: string | null = null;
|
||||
|
||||
// ---- Autorizaciones ----
|
||||
pacienteSeleccionado: any = null;
|
||||
ipsDisponibles: any[] = [];
|
||||
autorizantes: any[] = [];
|
||||
cupsDisponibles: any[] = [];
|
||||
cupsDisponibles: CupInfo[] = [];
|
||||
cupSeleccionado: CupInfo | null = null;
|
||||
buscandoCups = false;
|
||||
errorCups: string | null = null;
|
||||
verMasIps = false;
|
||||
departamentoInterno = '';
|
||||
observacionTraslado = '';
|
||||
autorizacionEditando: any | null = null;
|
||||
actualizandoEstado: Record<string, boolean> = {};
|
||||
|
||||
versionesPorAutorizacion: Record<string, AutorizacionVersion[]> = {};
|
||||
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
|
||||
// -------------------------
|
||||
@ -220,6 +148,7 @@ export class AutorizacionesComponent {
|
||||
this.cargandoVersiones = {};
|
||||
this.errorAutLista = null;
|
||||
this.cupsDisponibles = [];
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCups = null;
|
||||
|
||||
this.cargarIps(p.interno, this.verMasIps);
|
||||
@ -233,6 +162,7 @@ export class AutorizacionesComponent {
|
||||
this.errorAutorizacion = null;
|
||||
this.errorAutLista = null;
|
||||
this.cupsDisponibles = [];
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCups = null;
|
||||
this.verMasIps = false;
|
||||
this.departamentoInterno = '';
|
||||
@ -336,15 +266,17 @@ export class AutorizacionesComponent {
|
||||
const termino = String(this.formAutorizacion.cup_codigo || '').trim();
|
||||
if (!termino) {
|
||||
this.cupsDisponibles = [];
|
||||
this.cupSeleccionado = null;
|
||||
this.errorCups = 'Ingresa un código o descripción para buscar.';
|
||||
return;
|
||||
}
|
||||
|
||||
this.buscandoCups = true;
|
||||
this.errorCups = null;
|
||||
this.cupSeleccionado = null;
|
||||
|
||||
this.pacienteService
|
||||
.buscarCupsCubiertos(termino)
|
||||
.buscarCups(termino)
|
||||
.pipe(
|
||||
finalize(() => {
|
||||
this.buscandoCups = false;
|
||||
@ -352,22 +284,31 @@ export class AutorizacionesComponent {
|
||||
})
|
||||
)
|
||||
.subscribe({
|
||||
next: (data: any[]) => {
|
||||
next: (data: CupInfo[]) => {
|
||||
this.cupsDisponibles = data || [];
|
||||
const match = this.cupsDisponibles.find(
|
||||
(cup) => cup.codigo === this.formAutorizacion.cup_codigo
|
||||
);
|
||||
this.cupSeleccionado = match || null;
|
||||
if (this.cupsDisponibles.length === 0) {
|
||||
this.errorCups = 'No se encontraron CUPS con ese criterio.';
|
||||
}
|
||||
},
|
||||
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;
|
||||
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) : '';
|
||||
}
|
||||
|
||||
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 {
|
||||
const base = String(this.formAutorizacion.observacion || '').trim();
|
||||
const trasladoTexto = String(this.observacionTraslado || '').trim();
|
||||
@ -453,6 +457,7 @@ export class AutorizacionesComponent {
|
||||
tipo_autorizacion: autorizacion.tipo_autorizacion || 'consultas_externas',
|
||||
tipo_servicio: autorizacion.tipo_servicio || '',
|
||||
};
|
||||
this.cupSeleccionado = null;
|
||||
|
||||
this.onTipoAutorizacionChange();
|
||||
this.onIpsChange();
|
||||
@ -464,6 +469,7 @@ export class AutorizacionesComponent {
|
||||
cancelarEdicion(): void {
|
||||
this.autorizacionEditando = null;
|
||||
this.observacionTraslado = '';
|
||||
this.cupSeleccionado = null;
|
||||
this.formAutorizacion = {
|
||||
id_ips: '',
|
||||
numero_documento_autorizante: '',
|
||||
@ -495,7 +501,6 @@ export class AutorizacionesComponent {
|
||||
this.errorAutorizacion = 'Debe seleccionar un CUPS.';
|
||||
return;
|
||||
}
|
||||
|
||||
const tipoAutorizacion = String(
|
||||
this.formAutorizacion.tipo_autorizacion || 'consultas_externas'
|
||||
).toLowerCase();
|
||||
@ -633,7 +638,17 @@ export class AutorizacionesComponent {
|
||||
// -------------------------
|
||||
// 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.errorAutLista = null;
|
||||
|
||||
@ -701,6 +716,10 @@ export class AutorizacionesComponent {
|
||||
this.router.navigate(['/autorizaciones-por-fecha']);
|
||||
}
|
||||
|
||||
irACargarPacientes(): void {
|
||||
this.router.navigate(['/cargar-pacientes']);
|
||||
}
|
||||
|
||||
irADashboard(): void {
|
||||
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
|
||||
title="Cargar CUPS"
|
||||
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"
|
||||
backLabel="Volver"
|
||||
(back)="volverDashboard()"
|
||||
[showLogo]="false"
|
||||
></app-header>
|
||||
|
||||
<div class="card cups-card">
|
||||
|
||||
@ -38,6 +38,18 @@ export class CargarCupsComponent implements OnInit {
|
||||
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 {
|
||||
const input = event.target as HTMLInputElement;
|
||||
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;
|
||||
border-radius: 8px;
|
||||
margin: 20px 0 24px;
|
||||
background: white;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||||
background: var(--color-card);
|
||||
border: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-card);
|
||||
animation: slideIn 0.3s ease;
|
||||
}
|
||||
|
||||
.alert-error {
|
||||
border-left: 4px solid #dc2626;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border-left: 4px solid var(--color-error);
|
||||
background: var(--color-permission-no-bg);
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
@ -94,20 +95,21 @@
|
||||
}
|
||||
|
||||
.action-card {
|
||||
background: white;
|
||||
background: var(--color-card);
|
||||
border-radius: 12px;
|
||||
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;
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
border: 2px solid transparent;
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
|
||||
border-color: #1976d2;
|
||||
box-shadow: var(--shadow-float);
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
@ -117,8 +119,8 @@
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #1976d2;
|
||||
background: #e3f2fd;
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
@ -130,14 +132,14 @@
|
||||
.action-card h4,
|
||||
.action-card h3 {
|
||||
margin: 0 0 8px 0;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
font-size: 1.05rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.action-card p {
|
||||
margin: 0;
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
@ -146,7 +148,7 @@
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
background: #1976d2;
|
||||
background: var(--color-primary);
|
||||
color: white;
|
||||
padding: 4px 8px;
|
||||
border-radius: 12px;
|
||||
@ -171,12 +173,12 @@
|
||||
|
||||
.role-label {
|
||||
font-weight: 600;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
min-width: 140px;
|
||||
}
|
||||
|
||||
.role-value {
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
@ -186,36 +188,90 @@
|
||||
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 {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
background: var(--color-primary-soft);
|
||||
color: var(--color-primary);
|
||||
padding: 4px 12px;
|
||||
border-radius: 16px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
border: 1px solid #1976d2;
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
/* Permissions Summary */
|
||||
.permissions-summary {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.section-subtitle {
|
||||
margin: -8px 0 16px;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.permissions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.permission-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-items: flex-start;
|
||||
gap: 12px;
|
||||
padding: 12px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 8px;
|
||||
background: #f9fafb;
|
||||
background: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.permission-item.has-permission {
|
||||
background: #f0f9f0;
|
||||
border: 1px solid #dcfce7;
|
||||
background: var(--color-permission-yes-bg);
|
||||
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 {
|
||||
@ -226,11 +282,31 @@
|
||||
display: inline-flex;
|
||||
align-items: 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;
|
||||
color: #222222;
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.permission-meta {
|
||||
font-size: 0.82rem;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.excel-status {
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
<app-header
|
||||
title="SALUD UT"
|
||||
subtitle="Módulo de autorizaciones médicas"
|
||||
badgeText="SALUD UT"
|
||||
[showUserInfo]="true"
|
||||
[userName]="getNombreUsuario()"
|
||||
[userRole]="getNombreRolFormateado()"
|
||||
@ -50,7 +51,7 @@
|
||||
<div
|
||||
class="action-card"
|
||||
*ngIf="puedeCargarPacientes()"
|
||||
(click)="abrirCargadorPacientes(inputExcelPacientes)"
|
||||
(click)="irACargarPacientes()"
|
||||
>
|
||||
<span class="action-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||
@ -67,9 +68,7 @@
|
||||
</span>
|
||||
<h4>Cargar pacientes</h4>
|
||||
<p>
|
||||
{{ cargandoExcel
|
||||
? 'Cargando archivo de pacientes...'
|
||||
: 'Subir archivo Excel con datos de pacientes' }}
|
||||
Subir archivo Excel con datos de pacientes
|
||||
</p>
|
||||
<div class="admin-badge">Solo admin</div>
|
||||
</div>
|
||||
@ -100,11 +99,67 @@
|
||||
<div class="admin-badge">Solo admin</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
|
||||
class="action-card"
|
||||
(click)="irAVerAutorizacionesPorFecha()"
|
||||
*ngIf="puedeVerTodasAutorizaciones()"
|
||||
>
|
||||
<span class="action-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 24 24" role="img" aria-label="">
|
||||
@ -116,6 +171,28 @@
|
||||
</span>
|
||||
<h4>Autorizaciones por fecha</h4>
|
||||
<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>
|
||||
|
||||
@ -143,20 +220,6 @@
|
||||
</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>
|
||||
|
||||
<!-- User Role Information -->
|
||||
@ -171,20 +234,35 @@
|
||||
<!-- Sedes asignadas (solo para administrativos) -->
|
||||
<div
|
||||
class="role-item"
|
||||
*ngIf="
|
||||
currentUser.nombre_rol === 'administrativo_sede' &&
|
||||
getSedesUsuario().length > 0
|
||||
"
|
||||
*ngIf="currentUser.nombre_rol === 'administrativo_sede'"
|
||||
>
|
||||
<span class="role-label">Sedes asignadas:</span>
|
||||
<div class="sedes-list">
|
||||
<span
|
||||
class="sede-badge"
|
||||
*ngFor="let sede of getSedesUsuario()"
|
||||
[title]="sede.nombre_establecimiento"
|
||||
>
|
||||
{{ sede.nombre_establecimiento }}
|
||||
</span>
|
||||
<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
|
||||
class="sede-badge"
|
||||
*ngFor="let sede of getSedesMostradas()"
|
||||
[title]="sede.nombre_establecimiento"
|
||||
>
|
||||
{{ sede.nombre_establecimiento }}
|
||||
</span>
|
||||
</div>
|
||||
<ng-template #sinSedes>
|
||||
<span class="sedes-empty">Sin sedes asignadas</span>
|
||||
</ng-template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -193,10 +271,14 @@
|
||||
<!-- Permissions Summary -->
|
||||
<section class="permissions-summary card">
|
||||
<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="permission-item"
|
||||
[class.has-permission]="puedeGenerarAutorizaciones()"
|
||||
[class.no-permission]="!puedeGenerarAutorizaciones()"
|
||||
>
|
||||
<span class="permission-icon" *ngIf="puedeGenerarAutorizaciones(); else noPermGen">
|
||||
✓
|
||||
@ -204,25 +286,56 @@
|
||||
<ng-template #noPermGen>
|
||||
<span class="permission-icon">×</span>
|
||||
</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
|
||||
class="permission-item"
|
||||
[class.has-permission]="puedeCargarPacientes()"
|
||||
[class.has-permission]="puedeCargarAutorizacionesMasivas()"
|
||||
[class.no-permission]="!puedeCargarAutorizacionesMasivas()"
|
||||
>
|
||||
<span class="permission-icon" *ngIf="puedeCargarPacientes(); else noPermExcel">
|
||||
<span
|
||||
class="permission-icon"
|
||||
*ngIf="puedeCargarAutorizacionesMasivas(); else noPermMasivas"
|
||||
>
|
||||
✓
|
||||
</span>
|
||||
<ng-template #noPermExcel>
|
||||
<ng-template #noPermMasivas>
|
||||
<span class="permission-icon">×</span>
|
||||
</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
|
||||
class="permission-item"
|
||||
[class.has-permission]="puedeDescargarPdfs()"
|
||||
[class.no-permission]="!puedeDescargarPdfs()"
|
||||
>
|
||||
<span class="permission-icon" *ngIf="puedeDescargarPdfs(); else noPermPdf">
|
||||
✓
|
||||
@ -230,12 +343,16 @@
|
||||
<ng-template #noPermPdf>
|
||||
<span class="permission-icon">×</span>
|
||||
</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
|
||||
class="permission-item"
|
||||
[class.has-permission]="puedeVerTodasAutorizaciones()"
|
||||
[class.no-permission]="!puedeVerTodasAutorizaciones()"
|
||||
>
|
||||
<span class="permission-icon" *ngIf="puedeVerTodasAutorizaciones(); else noPermAll">
|
||||
✓
|
||||
@ -243,7 +360,95 @@
|
||||
<ng-template #noPermAll>
|
||||
<span class="permission-icon">×</span>
|
||||
</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>
|
||||
</section>
|
||||
|
||||
@ -1,16 +1,13 @@
|
||||
import {
|
||||
Component,
|
||||
OnInit,
|
||||
OnDestroy,
|
||||
ChangeDetectorRef
|
||||
OnDestroy
|
||||
} from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { Router, ActivatedRoute } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AuthService } from '../../services/auth';
|
||||
import { PacienteService } from '../../services/paciente';
|
||||
import { AppHeaderComponent } from '../shared/app-header/app-header';
|
||||
import { JobsService } from '../../services/jobs';
|
||||
|
||||
@Component({
|
||||
selector: 'app-dashboard',
|
||||
@ -23,18 +20,12 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
currentUser: any = null;
|
||||
errorMessage: string | null = null;
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
// ---- Carga de Excel ----
|
||||
cargandoExcel = false;
|
||||
estadoCargaExcel: string | null = null;
|
||||
mostrarTodasSedes = false;
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private router: Router,
|
||||
private route: ActivatedRoute,
|
||||
private pacienteService: PacienteService,
|
||||
private jobsService: JobsService,
|
||||
private cdr: ChangeDetectorRef
|
||||
private route: ActivatedRoute
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -98,10 +89,26 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
this.router.navigate(['/autorizaciones-por-fecha']);
|
||||
}
|
||||
|
||||
irAEstadisticasAutorizaciones(): void {
|
||||
this.router.navigate(['/estadisticas-autorizaciones']);
|
||||
}
|
||||
|
||||
irACargarCups(): void {
|
||||
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 {
|
||||
this.router.navigate(['/usuarios']);
|
||||
}
|
||||
@ -149,6 +156,10 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
return this.authService.isAdministrador();
|
||||
}
|
||||
|
||||
puedeCargarIpsReps(): boolean {
|
||||
return this.authService.isAdministrador();
|
||||
}
|
||||
|
||||
puedeDescargarPdfs(): boolean {
|
||||
return this.authService.puedeDescargarPdfs();
|
||||
}
|
||||
@ -157,14 +168,42 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
return this.authService.puedeVerTodasAutorizaciones();
|
||||
}
|
||||
|
||||
puedeVerEstadisticas(): boolean {
|
||||
return this.authService.isAdministrador();
|
||||
}
|
||||
|
||||
puedeGenerarAutorizaciones(): boolean {
|
||||
return this.authService.puedeGenerarAutorizaciones();
|
||||
}
|
||||
|
||||
puedeCargarAutorizacionesMasivas(): boolean {
|
||||
return this.authService.puedeGenerarAutorizaciones();
|
||||
}
|
||||
|
||||
puedeVerAutorizacionesPorFecha(): boolean {
|
||||
return this.authService.isLoggedIn();
|
||||
}
|
||||
|
||||
getSedesUsuario(): any[] {
|
||||
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 {
|
||||
this.errorMessage = null;
|
||||
}
|
||||
@ -178,83 +217,4 @@ export class DashboardComponent implements OnInit, OnDestroy {
|
||||
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 {
|
||||
margin: 4px 0 0;
|
||||
color: #666666;
|
||||
color: var(--color-text-muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@
|
||||
.form-row label {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.form-row input,
|
||||
@ -49,8 +50,10 @@
|
||||
.form-row textarea {
|
||||
padding: 8px 10px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ccc;
|
||||
border: 1px solid var(--color-input-border);
|
||||
font-size: 0.9rem;
|
||||
background: var(--color-input-bg);
|
||||
color: var(--color-text-main);
|
||||
}
|
||||
|
||||
.form-row input:focus,
|
||||
@ -67,7 +70,7 @@
|
||||
|
||||
.hint {
|
||||
font-size: 0.75rem;
|
||||
color: #777777;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
/* Tabla */
|
||||
@ -82,17 +85,17 @@
|
||||
}
|
||||
|
||||
.tabla-usuarios-wrapper::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
background: var(--color-surface-muted);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb {
|
||||
background: #c0c0c0;
|
||||
background: var(--color-border);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.tabla-usuarios-wrapper::-webkit-scrollbar-thumb:hover {
|
||||
background: #a0a0a0;
|
||||
background: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.tabla-usuarios thead {
|
||||
@ -106,6 +109,43 @@
|
||||
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) {
|
||||
.usuarios-grid {
|
||||
grid-template-columns: 1fr;
|
||||
|
||||
@ -3,11 +3,15 @@
|
||||
<app-header
|
||||
title="SALUD UT"
|
||||
subtitle="Gestión de Usuarios"
|
||||
badgeText="SALUD UT"
|
||||
[showUserInfo]="true"
|
||||
[userName]="getNombreUsuario()"
|
||||
[userRole]="getNombreRolFormateado()"
|
||||
[showLogout]="true"
|
||||
(logout)="logout()"
|
||||
[showBack]="true"
|
||||
backLabel="Volver"
|
||||
(back)="volverDashboard()"
|
||||
></app-header>
|
||||
|
||||
<header class="usuarios-header">
|
||||
@ -17,10 +21,7 @@
|
||||
Crear, activar y desactivar usuarios del sistema
|
||||
</p>
|
||||
</div>
|
||||
<button class="btn btn-secondary" (click)="volverDashboard()">
|
||||
Volver al dashboard
|
||||
</button>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
<section class="usuarios-grid">
|
||||
<!-- Formulario de creación -->
|
||||
@ -57,19 +58,6 @@
|
||||
</select>
|
||||
</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">
|
||||
<button class="btn btn-primary" (click)="crearUsuario()" [disabled]="creando">
|
||||
{{ creando ? 'Creando...' : 'Crear usuario' }}
|
||||
@ -160,9 +148,29 @@
|
||||
<tr *ngFor="let a of autorizantes; trackBy: trackByAutorizante">
|
||||
<td>{{ a.numero_documento }}</td>
|
||||
<td>{{ a.tipo_documento }}</td>
|
||||
<td>{{ a.nombre }}</td>
|
||||
<td>{{ a.telefono || '-' }}</td>
|
||||
<td>{{ a.cargo || '-' }}</td>
|
||||
<td>
|
||||
<input
|
||||
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>
|
||||
<span
|
||||
class="badge"
|
||||
@ -173,20 +181,45 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
*ngIf="a.activo"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="cambiarEstadoAutorizante(a, false)"
|
||||
<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
|
||||
*ngIf="a.activo"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="cambiarEstadoAutorizante(a, false)"
|
||||
>
|
||||
Desactivar
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!a.activo"
|
||||
class="btn btn-secondary btn-sm"
|
||||
(click)="cambiarEstadoAutorizante(a, true)"
|
||||
>
|
||||
Activar
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="inline-status ok"
|
||||
*ngIf="autorizanteEdicion[a.numero_documento]?.mensaje"
|
||||
>
|
||||
Desactivar
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!a.activo"
|
||||
class="btn btn-secondary btn-sm"
|
||||
(click)="cambiarEstadoAutorizante(a, true)"
|
||||
{{ autorizanteEdicion[a.numero_documento].mensaje }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-status error"
|
||||
*ngIf="autorizanteEdicion[a.numero_documento]?.error"
|
||||
>
|
||||
Activar
|
||||
</button>
|
||||
{{ autorizanteEdicion[a.numero_documento].error }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -210,6 +243,7 @@
|
||||
<th>Nombre</th>
|
||||
<th>Email</th>
|
||||
<th>Rol</th>
|
||||
<th>Nueva contrasena</th>
|
||||
<th>Estado</th>
|
||||
<th>Último login</th>
|
||||
<th>Acciones</th>
|
||||
@ -217,10 +251,45 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let u of usuarios; trackBy: trackByUsuario">
|
||||
<td>{{ u.username }}</td>
|
||||
<td>{{ u.nombre_completo }}</td>
|
||||
<td>{{ u.email }}</td>
|
||||
<td>{{ u.nombre_rol }}</td>
|
||||
<td>
|
||||
<input
|
||||
class="input-inline"
|
||||
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>
|
||||
<span
|
||||
class="badge"
|
||||
@ -234,20 +303,45 @@
|
||||
{{ u.ultimo_login ? (u.ultimo_login | date:'yyyy-MM-dd HH:mm') : '—' }}
|
||||
</td>
|
||||
<td>
|
||||
<button
|
||||
*ngIf="u.activo"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="cambiarEstadoUsuario(u, false)"
|
||||
<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
|
||||
*ngIf="u.activo"
|
||||
class="btn btn-danger btn-sm"
|
||||
(click)="cambiarEstadoUsuario(u, false)"
|
||||
>
|
||||
Desactivar
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!u.activo"
|
||||
class="btn btn-secondary btn-sm"
|
||||
(click)="cambiarEstadoUsuario(u, true)"
|
||||
>
|
||||
Activar
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="inline-status ok"
|
||||
*ngIf="usuarioEdicion[u.id_usuario]?.mensaje"
|
||||
>
|
||||
Desactivar
|
||||
</button>
|
||||
<button
|
||||
*ngIf="!u.activo"
|
||||
class="btn btn-secondary btn-sm"
|
||||
(click)="cambiarEstadoUsuario(u, true)"
|
||||
{{ usuarioEdicion[u.id_usuario].mensaje }}
|
||||
</div>
|
||||
<div
|
||||
class="inline-status error"
|
||||
*ngIf="usuarioEdicion[u.id_usuario]?.error"
|
||||
>
|
||||
Activar
|
||||
</button>
|
||||
{{ usuarioEdicion[u.id_usuario].error }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
@ -3,17 +3,39 @@ import { CommonModule } from '@angular/common';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import {
|
||||
AuthService,
|
||||
ActualizarUsuarioPayload,
|
||||
RegisterRequest,
|
||||
Rol
|
||||
} from '../../services/auth';
|
||||
import {
|
||||
Autorizante,
|
||||
ActualizarAutorizantePayload,
|
||||
CrearAutorizantePayload,
|
||||
PacienteService
|
||||
} from '../../services/paciente';
|
||||
import { Router } from '@angular/router';
|
||||
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({
|
||||
selector: 'app-usuarios',
|
||||
standalone: true,
|
||||
@ -26,6 +48,8 @@ export class UsuariosComponent implements OnInit {
|
||||
usuarios: any[] = [];
|
||||
roles: Rol[] = [];
|
||||
autorizantes: Autorizante[] = [];
|
||||
autorizanteEdicion: Record<number, AutorizanteEdicion> = {};
|
||||
usuarioEdicion: Record<number, UsuarioEdicion> = {};
|
||||
|
||||
cargandoUsuarios = false;
|
||||
errorUsuarios: string | null = null;
|
||||
@ -43,8 +67,6 @@ export class UsuariosComponent implements OnInit {
|
||||
mensajeAutorizanteOk: string | null = null;
|
||||
mensajeAutorizanteError: string | null = null;
|
||||
|
||||
// para administrativos, códigos separados por coma
|
||||
sedesTexto = '';
|
||||
|
||||
nuevoUsuario: RegisterRequest = {
|
||||
username: '',
|
||||
@ -134,6 +156,7 @@ export class UsuariosComponent implements OnInit {
|
||||
this.authService.getUsuarios(this.limiteUsuarios, 0).subscribe({
|
||||
next: (usuarios) => {
|
||||
this.usuarios = usuarios;
|
||||
this.prepararEdicionUsuarios();
|
||||
this.avisoUsuarios = usuarios.length >= this.limiteUsuarios;
|
||||
this.cargandoUsuarios = false;
|
||||
this.cdr.detectChanges();
|
||||
@ -161,17 +184,7 @@ export class UsuariosComponent implements OnInit {
|
||||
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;
|
||||
|
||||
@ -189,7 +202,6 @@ export class UsuariosComponent implements OnInit {
|
||||
id_rol: 2,
|
||||
sedes: []
|
||||
};
|
||||
this.sedesTexto = '';
|
||||
|
||||
// Recargar lista sin refrescar la página
|
||||
this.cargarUsuarios();
|
||||
@ -290,6 +302,7 @@ export class UsuariosComponent implements OnInit {
|
||||
this.pacienteService.obtenerAutorizantesAdmin().subscribe({
|
||||
next: (autorizantes) => {
|
||||
this.autorizantes = autorizantes;
|
||||
this.prepararEdicionAutorizantes();
|
||||
this.cargandoAutorizantes = false;
|
||||
this.cdr.detectChanges();
|
||||
},
|
||||
@ -328,4 +341,219 @@ export class UsuariosComponent implements OnInit {
|
||||
trackByAutorizante(_index: number, autorizante: Autorizante): number | string {
|
||||
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(/\/+$/, '');
|
||||
|
||||
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[];
|
||||
}
|
||||
|
||||
export interface ActualizarUsuarioPayload {
|
||||
username?: string;
|
||||
password?: string;
|
||||
email?: string;
|
||||
nombre_completo?: string;
|
||||
id_rol?: number;
|
||||
}
|
||||
|
||||
/* Interfaz Rol para la pantalla de usuarios */
|
||||
export interface Rol {
|
||||
id_rol: number;
|
||||
@ -42,6 +50,25 @@ export interface Rol {
|
||||
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({
|
||||
providedIn: 'root'
|
||||
})
|
||||
@ -55,6 +82,7 @@ export class AuthService {
|
||||
|
||||
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
|
||||
private logoutTimeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
@ -70,6 +98,7 @@ export class AuthService {
|
||||
if (token && user) {
|
||||
this.currentUserSubject.next(user);
|
||||
this.isAuthenticatedSubject.next(true);
|
||||
this.scheduleTokenExpiry(token);
|
||||
} else {
|
||||
this.clearAuth();
|
||||
}
|
||||
@ -78,7 +107,7 @@ export class AuthService {
|
||||
// =========== LOGIN ===========
|
||||
|
||||
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 => {
|
||||
this.setAuth(response.token, response.usuario);
|
||||
}),
|
||||
@ -93,7 +122,7 @@ export class AuthService {
|
||||
|
||||
register(userData: RegisterRequest): Observable<any> {
|
||||
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) => {
|
||||
console.error('Error en register:', error);
|
||||
return throwError(() => error);
|
||||
@ -112,7 +141,7 @@ export class AuthService {
|
||||
|
||||
verifyToken(): Observable<{ usuario: User }> {
|
||||
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 => {
|
||||
this.updateUser(response.usuario);
|
||||
})
|
||||
@ -130,15 +159,54 @@ export class AuthService {
|
||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
||||
this.currentUserSubject.next(user);
|
||||
this.isAuthenticatedSubject.next(true);
|
||||
this.scheduleTokenExpiry(token);
|
||||
}
|
||||
|
||||
private clearAuth(): void {
|
||||
this.clearLogoutTimer();
|
||||
localStorage.removeItem(this.TOKEN_KEY);
|
||||
localStorage.removeItem(this.USER_KEY);
|
||||
this.currentUserSubject.next(null);
|
||||
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 {
|
||||
localStorage.setItem(this.USER_KEY, JSON.stringify(user));
|
||||
this.currentUserSubject.next(user);
|
||||
@ -183,7 +251,8 @@ export class AuthService {
|
||||
}
|
||||
|
||||
puedeDescargarPdfs(): boolean {
|
||||
return this.isAdministrador();
|
||||
const user = this.getCurrentUser();
|
||||
return !!user && (user.nombre_rol === 'administrador' || user.nombre_rol === 'administrativo_sede');
|
||||
}
|
||||
|
||||
puedeVerTodasAutorizaciones(): boolean {
|
||||
@ -213,18 +282,27 @@ export class AuthService {
|
||||
getUsuarios(limit = 200, offset = 0): Observable<any[]> {
|
||||
const headers = this.getAuthHeaders();
|
||||
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> {
|
||||
const headers = this.getAuthHeaders();
|
||||
return this.http.patch(
|
||||
`${this.API_URL}/api/usuarios/${idUsuario}/estado`,
|
||||
`${this.API_URL}/usuarios/${idUsuario}/estado`,
|
||||
{ activo },
|
||||
{ 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
|
||||
actualizarEstadoUsuario(idUsuario: number, activo: boolean): Observable<any> {
|
||||
return this.cambiarEstadoUsuario(idUsuario, activo);
|
||||
@ -239,13 +317,27 @@ export class AuthService {
|
||||
): Observable<any[]> {
|
||||
const headers = this.getAuthHeaders();
|
||||
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
|
||||
getRoles(): Observable<Rol[]> {
|
||||
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(
|
||||
@ -258,7 +350,7 @@ export class AuthService {
|
||||
payload.version = version;
|
||||
}
|
||||
return this.http.post<JobResponse>(
|
||||
`${this.API_URL}/api/jobs/autorizacion-pdf`,
|
||||
`${this.API_URL}/jobs/autorizacion-pdf`,
|
||||
payload,
|
||||
{ headers }
|
||||
);
|
||||
@ -267,7 +359,7 @@ export class AuthService {
|
||||
crearJobZipAutorizaciones(fechaInicio: string, fechaFin: string): Observable<JobResponse> {
|
||||
const headers = this.getAuthHeaders();
|
||||
return this.http.post<JobResponse>(
|
||||
`${this.API_URL}/api/jobs/autorizaciones-zip`,
|
||||
`${this.API_URL}/jobs/autorizaciones-zip`,
|
||||
{ fecha_inicio: fechaInicio, fecha_fin: fechaFin },
|
||||
{ headers }
|
||||
);
|
||||
|
||||
@ -4,6 +4,11 @@ export interface JobError {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface JobRowError {
|
||||
fila?: number;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export interface JobResult {
|
||||
ok?: boolean;
|
||||
mensaje?: string;
|
||||
@ -11,6 +16,20 @@ export interface JobResult {
|
||||
antiguos?: number | null;
|
||||
referencia?: 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;
|
||||
fileName?: string;
|
||||
contentType?: string;
|
||||
|
||||
@ -19,7 +19,7 @@ export class JobsService {
|
||||
|
||||
getJob(jobId: string): Observable<JobResponse> {
|
||||
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> {
|
||||
@ -34,7 +34,7 @@ export class JobsService {
|
||||
|
||||
downloadJobFile(jobId: string): Observable<Blob> {
|
||||
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,
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
@ -35,6 +35,7 @@ export interface Ips {
|
||||
telefono?: string;
|
||||
departamento: string;
|
||||
municipio: string;
|
||||
tiene_convenio?: boolean | null;
|
||||
}
|
||||
|
||||
export interface Autorizante {
|
||||
@ -55,6 +56,12 @@ export interface CrearAutorizantePayload {
|
||||
activo?: boolean;
|
||||
}
|
||||
|
||||
export interface ActualizarAutorizantePayload {
|
||||
nombre?: string;
|
||||
telefono?: string | null;
|
||||
cargo?: string | null;
|
||||
}
|
||||
|
||||
export interface CrearAutorizacionPayload {
|
||||
interno: string;
|
||||
id_ips: number;
|
||||
@ -70,6 +77,7 @@ export interface RespuestaAutorizacion {
|
||||
numero_autorizacion: string;
|
||||
fecha_autorizacion: string;
|
||||
version?: number;
|
||||
estado_autorizacion?: string;
|
||||
}
|
||||
|
||||
export interface AutorizacionListado {
|
||||
@ -83,11 +91,13 @@ export interface AutorizacionListado {
|
||||
tipo_autorizacion?: string | null;
|
||||
tipo_servicio?: string | null;
|
||||
version?: number | null;
|
||||
estado_autorizacion?: string | null;
|
||||
id_ips?: number | null;
|
||||
numero_documento_autorizante?: number | null;
|
||||
nombre_ips: string;
|
||||
municipio: string;
|
||||
departamento: string;
|
||||
municipio?: string | null;
|
||||
departamento?: string | null;
|
||||
ips_tiene_convenio?: boolean | null;
|
||||
nombre_autorizante: string;
|
||||
}
|
||||
|
||||
@ -102,6 +112,14 @@ export interface AutorizacionVersionResponse {
|
||||
versiones: AutorizacionVersion[];
|
||||
}
|
||||
|
||||
export interface CupInfo {
|
||||
codigo: string;
|
||||
descripcion: string;
|
||||
nivel?: string | null;
|
||||
especialidad?: string | null;
|
||||
cubierto: boolean;
|
||||
}
|
||||
|
||||
// ====== Servicio ======
|
||||
|
||||
@Injectable({
|
||||
@ -124,7 +142,7 @@ export class PacienteService {
|
||||
|
||||
buscarPorDocumento(numero_documento: string): Observable<Paciente[]> {
|
||||
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,
|
||||
headers: this.getAuthHeaders()
|
||||
});
|
||||
@ -132,7 +150,7 @@ export class PacienteService {
|
||||
|
||||
buscarPorInterno(interno: string): Observable<Paciente[]> {
|
||||
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,
|
||||
headers: this.getAuthHeaders()
|
||||
});
|
||||
@ -140,7 +158,7 @@ export class PacienteService {
|
||||
|
||||
buscarPorNombre(nombre: string): Observable<Paciente[]> {
|
||||
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,
|
||||
headers: this.getAuthHeaders()
|
||||
});
|
||||
@ -153,37 +171,48 @@ export class PacienteService {
|
||||
if (verTodas) {
|
||||
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,
|
||||
headers: this.getAuthHeaders()
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
}
|
||||
|
||||
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()
|
||||
});
|
||||
}
|
||||
|
||||
actualizarEstadoAutorizante(numeroDocumento: number, activo: boolean): Observable<any> {
|
||||
return this.http.patch(
|
||||
`${this.API_URL}/api/autorizantes/${numeroDocumento}/estado`,
|
||||
`${this.API_URL}/autorizantes/${numeroDocumento}/estado`,
|
||||
{ activo },
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
}
|
||||
|
||||
actualizarDatosAutorizante(
|
||||
numeroDocumento: number,
|
||||
payload: ActualizarAutorizantePayload
|
||||
): Observable<any> {
|
||||
return this.http.patch(
|
||||
`${this.API_URL}/autorizantes/${numeroDocumento}`,
|
||||
payload,
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Autorizaciones ----
|
||||
|
||||
crearAutorizacion(payload: CrearAutorizacionPayload): Observable<RespuestaAutorizacion> {
|
||||
return this.http.post<RespuestaAutorizacion>(
|
||||
`${this.API_URL}/api/autorizaciones`,
|
||||
`${this.API_URL}/autorizaciones`,
|
||||
payload,
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
@ -194,7 +223,7 @@ export class PacienteService {
|
||||
payload: CrearAutorizacionPayload
|
||||
): Observable<RespuestaAutorizacion> {
|
||||
return this.http.put<RespuestaAutorizacion>(
|
||||
`${this.API_URL}/api/autorizaciones/${numeroAutorizacion}`,
|
||||
`${this.API_URL}/autorizaciones/${numeroAutorizacion}`,
|
||||
payload,
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
@ -202,7 +231,7 @@ export class PacienteService {
|
||||
|
||||
obtenerVersionesAutorizacion(numeroAutorizacion: string): Observable<AutorizacionVersionResponse> {
|
||||
return this.http.get<AutorizacionVersionResponse>(
|
||||
`${this.API_URL}/api/autorizaciones/${numeroAutorizacion}/versiones`,
|
||||
`${this.API_URL}/autorizaciones/${numeroAutorizacion}/versiones`,
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
}
|
||||
@ -210,7 +239,7 @@ export class PacienteService {
|
||||
obtenerAutorizacionesPorInterno(interno: string): Observable<AutorizacionListado[]> {
|
||||
const params = new HttpParams().set('interno', interno);
|
||||
return this.http.get<AutorizacionListado[]>(
|
||||
`${this.API_URL}/api/autorizaciones`,
|
||||
`${this.API_URL}/autorizaciones`,
|
||||
{ params, headers: this.getAuthHeaders() }
|
||||
);
|
||||
}
|
||||
@ -229,7 +258,7 @@ export class PacienteService {
|
||||
|
||||
// 3. Hacemos la petición con estos headers limpios.
|
||||
return this.http.post<JobResponse>(
|
||||
`${this.API_URL}/api/cargar-excel-pacientes`,
|
||||
`${this.API_URL}/cargar-excel-pacientes`,
|
||||
formData,
|
||||
{ headers }
|
||||
);
|
||||
@ -242,7 +271,46 @@ export class PacienteService {
|
||||
});
|
||||
|
||||
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,
|
||||
{ headers }
|
||||
);
|
||||
@ -250,18 +318,49 @@ export class PacienteService {
|
||||
|
||||
crearAutorizante(payload: CrearAutorizantePayload): Observable<any> {
|
||||
return this.http.post(
|
||||
`${this.API_URL}/api/autorizantes`,
|
||||
`${this.API_URL}/autorizantes`,
|
||||
payload,
|
||||
{ headers: this.getAuthHeaders() }
|
||||
);
|
||||
}
|
||||
|
||||
buscarCupsCubiertos(termino: string): Observable<any[]> {
|
||||
const params = new HttpParams().set('q', termino);
|
||||
return this.http.get<any[]>(`${this.API_URL}/api/cups-cubiertos`, {
|
||||
buscarCups(termino: string, limit = 200): Observable<CupInfo[]> {
|
||||
const params = new HttpParams()
|
||||
.set('q', termino)
|
||||
.set('limit', String(limit));
|
||||
return this.http.get<CupInfo[]>(`${this.API_URL}/cups`, {
|
||||
params,
|
||||
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 { routes } from './app/app.routes';
|
||||
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 { AuthInterceptor } from './app/interceptors/auth.interceptor';
|
||||
|
||||
bootstrapApplication(AppComponent, {
|
||||
providers: [
|
||||
@ -15,6 +16,11 @@ bootstrapApplication(AppComponent, {
|
||||
HttpClientModule,
|
||||
FormsModule,
|
||||
ReactiveFormsModule
|
||||
)
|
||||
),
|
||||
{
|
||||
provide: HTTP_INTERCEPTORS,
|
||||
useClass: AuthInterceptor,
|
||||
multi: true
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
@ -13,12 +13,23 @@
|
||||
--color-bg: #f5f5f5;
|
||||
--color-card: #ffffff;
|
||||
--color-border: #e0e0e0;
|
||||
--color-surface: #f8fafc;
|
||||
--color-surface-alt: #f1f5f9;
|
||||
--color-surface-muted: #eef2f7;
|
||||
--color-surface-strong: #e7eef7;
|
||||
|
||||
--color-text-main: #222222;
|
||||
--color-text-muted: #666666;
|
||||
|
||||
--color-success: #2e7d32;
|
||||
--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-row: #ffffff;
|
||||
@ -45,25 +56,33 @@
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-primary: #4fa3ff;
|
||||
--color-primary-dark: #2d7dcc;
|
||||
--color-primary-soft: #1d2b3b;
|
||||
--color-header-grad-2: #1f4b87;
|
||||
--color-primary: #3c7fc1;
|
||||
--color-primary-dark: #2f628f;
|
||||
--color-primary-soft: #1b2a3b;
|
||||
--color-header-grad-2: #1e3f66;
|
||||
|
||||
--color-bg: #0f141a;
|
||||
--color-card: #151c24;
|
||||
--color-border: #2a3440;
|
||||
--color-card: #121a24;
|
||||
--color-border: #1f2a36;
|
||||
--color-surface: #0f1621;
|
||||
--color-surface-alt: #151e2a;
|
||||
--color-surface-muted: #1b2635;
|
||||
--color-surface-strong: #1f2c3d;
|
||||
|
||||
--color-text-main: #e7edf5;
|
||||
--color-text-muted: #a8b3c2;
|
||||
--color-text-muted: #a1adbd;
|
||||
|
||||
--color-success: #3bb273;
|
||||
--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-row: #151c24;
|
||||
--color-table-row-alt: #19222c;
|
||||
--color-table-hover: #223046;
|
||||
--color-table-head: #1b2430;
|
||||
--color-table-row: #121a24;
|
||||
--color-table-row-alt: #16202b;
|
||||
--color-table-hover: #1f2a36;
|
||||
|
||||
--color-input-bg: #111821;
|
||||
--color-input-border: #2c3a4a;
|
||||
@ -77,6 +96,9 @@
|
||||
--color-cup-item-bg: #151c24;
|
||||
--color-cup-item-border: #273241;
|
||||
--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);
|
||||
border-radius: var(--radius-card);
|
||||
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 {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user