readme
This commit is contained in:
parent
4444dbe92b
commit
0ba872401f
57
README.md
57
README.md
@ -37,20 +37,23 @@ tar -xzf /opt/saludut/saludut-frontend.tar.gz -C /opt/saludut/
|
||||
```
|
||||
|
||||
### 4) Inicializar esquema y datos
|
||||
Este repo no incluye el esquema base (tablas `usuario`, `rol`, `autorizacion`, etc). Restaura tu dump base primero.
|
||||
|
||||
Luego ejecuta los SQL que si estan en el repo, en este orden:
|
||||
Primero genera el hash del admin y reemplaza `<HASH_BCRYPT>` en `backend/src/comandos.sql`:
|
||||
```bash
|
||||
node /opt/saludut/backend/src/generate-hash.js
|
||||
```
|
||||
|
||||
Luego ejecuta el setup unico (esquema + CUPS + roles/admin):
|
||||
```bash
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/setup.sql
|
||||
```
|
||||
|
||||
Opcional: cargar datos masivos desde archivos:
|
||||
```bash
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/cups_schema.sql
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/cups_referencia.sql
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/cups_cubiertos.sql
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/establecimiento.sql
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/paciente.sql
|
||||
psql -U saludut_user -d saludut_db -f /opt/saludut/backend/src/ingreso.sql
|
||||
```
|
||||
|
||||
Si tu esquema tiene restricciones, ajusta el orden de los inserts para respetar las FK.
|
||||
|
||||
### 5) Backend (.env)
|
||||
Crear `backend/.env` en el servidor:
|
||||
```
|
||||
@ -61,28 +64,17 @@ DB_PASSWORD=TU_PASSWORD_FUERTE
|
||||
DB_NAME=saludut_db
|
||||
JWT_SECRET=CAMBIA_ESTE_SECRETO
|
||||
JWT_EXPIRES_IN=24h
|
||||
PORT=3000
|
||||
PORT=45231
|
||||
SOFFICE_PATH=/usr/bin/soffice
|
||||
PYTHON_PATH=python3
|
||||
ADMIN_USER=admin
|
||||
ADMIN_PASS=admin123
|
||||
ADMIN_EMAIL=admin@saludut.gov.co
|
||||
ADMIN_NAME=Administrador Sistema
|
||||
```
|
||||
|
||||
### 6) Crear usuario admin (manual)
|
||||
Generar hash bcrypt:
|
||||
```bash
|
||||
node /opt/saludut/backend/src/generate-hash.js
|
||||
```
|
||||
|
||||
Luego en PostgreSQL:
|
||||
```sql
|
||||
SELECT id_rol FROM rol WHERE nombre_rol = 'administrador';
|
||||
|
||||
INSERT INTO usuario
|
||||
(username, email, password_hash, nombre_completo, id_rol, activo, fecha_creacion)
|
||||
VALUES
|
||||
('admin', 'admin@saludut.local', '<HASH_BCRYPT>', 'Administrador Sistema', <ID_ROL>, true, NOW());
|
||||
```
|
||||
|
||||
Si tu esquema tiene columnas obligatorias adicionales, completa esos campos.
|
||||
Si defines `ADMIN_USER` y `ADMIN_PASS` en `.env`, el backend crea/actualiza el admin automaticamente al iniciar.
|
||||
|
||||
### 7) Backend (instalar y correr)
|
||||
```bash
|
||||
@ -135,13 +127,20 @@ El frontend lee la base desde `window.__SALUDUT_CONFIG__`:
|
||||
</script>
|
||||
```
|
||||
|
||||
Si el backend esta en otro dominio, cambia `apiBaseUrl` antes de publicar.
|
||||
Si no usas proxy y expones el puerto directo:
|
||||
```html
|
||||
<script>
|
||||
window.__SALUDUT_CONFIG__ = {
|
||||
apiBaseUrl: 'https://autorizaciones.saludut.com:45231'
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
### 10) Nginx + SSL (ejemplo)
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name saludut.tu-dominio.com;
|
||||
server_name autorizaciones.saludut.com;
|
||||
|
||||
root /var/www/saludut;
|
||||
index index.html;
|
||||
@ -151,7 +150,7 @@ server {
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:3000/;
|
||||
proxy_pass http://127.0.0.1:45231/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
@ -162,7 +161,7 @@ server {
|
||||
Luego:
|
||||
```bash
|
||||
rc-service nginx start
|
||||
certbot --nginx -d saludut.tu-dominio.com
|
||||
certbot --nginx -d autorizaciones.saludut.com
|
||||
```
|
||||
|
||||
## Desarrollo
|
||||
|
||||
27
backend/src/comandos.sql
Normal file
27
backend/src/comandos.sql
Normal file
@ -0,0 +1,27 @@
|
||||
BEGIN;
|
||||
|
||||
-- Roles base
|
||||
INSERT INTO rol (nombre_rol, descripcion)
|
||||
VALUES
|
||||
('administrador', 'Puede cargar pacientes, descargar PDFs y ver todas las autorizaciones'),
|
||||
('administrativo_sede', 'Solo puede generar autorizaciones para su sede')
|
||||
ON CONFLICT (nombre_rol) DO NOTHING;
|
||||
|
||||
-- Usuario admin (reemplaza <HASH_BCRYPT> con el generado)
|
||||
INSERT INTO usuario (username, email, password_hash, nombre_completo, id_rol, activo)
|
||||
VALUES (
|
||||
'admin',
|
||||
'admin@saludut.gov.co',
|
||||
'<HASH_BCRYPT>',
|
||||
'Administrador Sistema',
|
||||
(SELECT id_rol FROM rol WHERE nombre_rol = 'administrador'),
|
||||
true
|
||||
)
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
email = EXCLUDED.email,
|
||||
password_hash = EXCLUDED.password_hash,
|
||||
nombre_completo = EXCLUDED.nombre_completo,
|
||||
id_rol = EXCLUDED.id_rol,
|
||||
activo = true;
|
||||
|
||||
COMMIT;
|
||||
207
backend/src/schema.sql
Normal file
207
backend/src/schema.sql
Normal file
@ -0,0 +1,207 @@
|
||||
BEGIN;
|
||||
|
||||
-- ===== Paciente =====
|
||||
CREATE TABLE IF NOT EXISTS paciente (
|
||||
interno text PRIMARY KEY NOT NULL,
|
||||
tipo_documento text,
|
||||
numero_documento text,
|
||||
primer_apellido text,
|
||||
segundo_apellido text,
|
||||
primer_nombre text,
|
||||
segundo_nombre text,
|
||||
fecha_nacimiento date,
|
||||
edad numeric,
|
||||
sexo text,
|
||||
activo boolean DEFAULT true
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION calcular_edad()
|
||||
RETURNS trigger AS $$
|
||||
BEGIN
|
||||
NEW.edad := EXTRACT(YEAR FROM age(current_date, NEW.fecha_nacimiento))::int;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_calcular_edad ON paciente;
|
||||
CREATE TRIGGER trg_calcular_edad
|
||||
BEFORE INSERT OR UPDATE OF fecha_nacimiento
|
||||
ON paciente
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION calcular_edad();
|
||||
|
||||
-- ===== Establecimiento =====
|
||||
CREATE TABLE IF NOT EXISTS establecimiento (
|
||||
codigo_establecimiento text PRIMARY KEY,
|
||||
nombre_establecimiento text,
|
||||
epc_ciudad text,
|
||||
epc_departamento text,
|
||||
regional text,
|
||||
regional_normalizada text
|
||||
);
|
||||
|
||||
-- ===== IPS =====
|
||||
CREATE TABLE IF NOT EXISTS ips (
|
||||
id_ips serial PRIMARY KEY,
|
||||
nit text,
|
||||
nombre_ips text,
|
||||
codigo_ips text,
|
||||
direccion text,
|
||||
telefono text,
|
||||
departamento text,
|
||||
municipio text
|
||||
);
|
||||
|
||||
-- ===== Ingreso =====
|
||||
CREATE TABLE IF NOT EXISTS ingreso (
|
||||
interno text PRIMARY KEY REFERENCES paciente(interno),
|
||||
codigo_establecimiento text REFERENCES establecimiento(codigo_establecimiento),
|
||||
estado text,
|
||||
fecha_ingreso date,
|
||||
nacionalidad text,
|
||||
tiempo_reclusion text
|
||||
);
|
||||
|
||||
-- ===== Autorizante =====
|
||||
CREATE TABLE IF NOT EXISTS autorizante (
|
||||
numero_documento bigint PRIMARY KEY,
|
||||
tipo_documento text NOT NULL,
|
||||
nombre text NOT NULL,
|
||||
telefono text,
|
||||
cargo text,
|
||||
activo boolean DEFAULT true
|
||||
);
|
||||
|
||||
-- ===== Roles y usuarios =====
|
||||
CREATE TABLE IF NOT EXISTS rol (
|
||||
id_rol SERIAL PRIMARY KEY,
|
||||
nombre_rol VARCHAR(50) UNIQUE NOT NULL,
|
||||
descripcion TEXT,
|
||||
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS usuario (
|
||||
id_usuario SERIAL PRIMARY KEY,
|
||||
username VARCHAR(50) UNIQUE NOT NULL,
|
||||
email VARCHAR(100) UNIQUE NOT NULL,
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
nombre_completo VARCHAR(200) NOT NULL,
|
||||
id_rol INTEGER NOT NULL REFERENCES rol(id_rol),
|
||||
activo BOOLEAN DEFAULT true,
|
||||
fecha_creacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
ultimo_login TIMESTAMP,
|
||||
intentos_fallidos INTEGER DEFAULT 0,
|
||||
bloqueado_hasta TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS usuario_sede (
|
||||
id_usuario_sede SERIAL PRIMARY KEY,
|
||||
id_usuario INTEGER NOT NULL REFERENCES usuario(id_usuario),
|
||||
codigo_establecimiento VARCHAR(50) NOT NULL REFERENCES establecimiento(codigo_establecimiento),
|
||||
fecha_asignacion TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(id_usuario, codigo_establecimiento)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_usuario_username ON usuario(username);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuario_email ON usuario(email);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuario_rol ON usuario(id_rol);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuario_sede_usuario ON usuario_sede(id_usuario);
|
||||
CREATE INDEX IF NOT EXISTS idx_usuario_sede_sede ON usuario_sede(codigo_establecimiento);
|
||||
|
||||
-- ===== Consecutivo autorizacion =====
|
||||
CREATE TABLE IF NOT EXISTS consecutivo_autorizacion (
|
||||
id smallint PRIMARY KEY,
|
||||
codigo text NOT NULL
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION next_numero_autorizacion()
|
||||
RETURNS text AS $$
|
||||
DECLARE
|
||||
actual text;
|
||||
prefijo constant text := 'UTUSCPG';
|
||||
letra text;
|
||||
num int;
|
||||
nuevo text;
|
||||
BEGIN
|
||||
SELECT codigo INTO actual
|
||||
FROM consecutivo_autorizacion
|
||||
WHERE id = 1
|
||||
FOR UPDATE;
|
||||
|
||||
IF actual IS NULL THEN
|
||||
actual := prefijo || 'B00';
|
||||
END IF;
|
||||
|
||||
letra := substring(actual from 8 for 1);
|
||||
num := substring(actual from 9 for 2)::int;
|
||||
|
||||
IF num < 99 THEN
|
||||
num := num + 1;
|
||||
ELSE
|
||||
letra := chr(ascii(letra) + 1);
|
||||
num := 1;
|
||||
END IF;
|
||||
|
||||
nuevo := prefijo || letra || LPAD(num::text, 2, '0');
|
||||
|
||||
UPDATE consecutivo_autorizacion
|
||||
SET codigo = nuevo
|
||||
WHERE id = 1;
|
||||
|
||||
RETURN nuevo;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- ===== Autorizacion =====
|
||||
CREATE TABLE IF NOT EXISTS autorizacion (
|
||||
numero_autorizacion text PRIMARY KEY DEFAULT next_numero_autorizacion(),
|
||||
interno text NOT NULL REFERENCES paciente(interno),
|
||||
id_ips integer NOT NULL REFERENCES ips(id_ips),
|
||||
numero_documento_autorizante bigint NOT NULL REFERENCES autorizante(numero_documento),
|
||||
fecha_autorizacion date NOT NULL DEFAULT current_date,
|
||||
observacion text,
|
||||
cup_codigo varchar(20),
|
||||
tipo_autorizacion varchar(50) NOT NULL DEFAULT 'consultas_externas',
|
||||
tipo_servicio varchar(50),
|
||||
version integer NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
INSERT INTO consecutivo_autorizacion (id, codigo)
|
||||
VALUES (1, COALESCE((SELECT MAX(numero_autorizacion) FROM autorizacion), 'UTUSCPGB00'))
|
||||
ON CONFLICT (id) DO NOTHING;
|
||||
|
||||
CREATE OR REPLACE FUNCTION asegurar_numero_unico()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
IF NEW.numero_autorizacion IS NULL OR NEW.numero_autorizacion = '' THEN
|
||||
NEW.numero_autorizacion := next_numero_autorizacion();
|
||||
END IF;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_asegurar_numero_unico ON autorizacion;
|
||||
CREATE TRIGGER trg_asegurar_numero_unico
|
||||
BEFORE INSERT ON autorizacion
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION asegurar_numero_unico();
|
||||
|
||||
-- ===== Versiones de autorizacion =====
|
||||
CREATE TABLE IF NOT EXISTS autorizacion_version (
|
||||
id SERIAL PRIMARY KEY,
|
||||
numero_autorizacion VARCHAR(50) NOT NULL,
|
||||
version INTEGER NOT NULL,
|
||||
id_ips INTEGER NOT NULL,
|
||||
numero_documento_autorizante BIGINT NOT NULL,
|
||||
fecha_autorizacion DATE,
|
||||
observacion TEXT,
|
||||
cup_codigo VARCHAR(20),
|
||||
tipo_autorizacion VARCHAR(50),
|
||||
tipo_servicio VARCHAR(50),
|
||||
fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS autorizacion_version_unique
|
||||
ON autorizacion_version (numero_autorizacion, version);
|
||||
|
||||
COMMIT;
|
||||
@ -265,6 +265,64 @@ const ensureAutorizacionVersionTables = async () => {
|
||||
`);
|
||||
};
|
||||
|
||||
const ensureAdminFromEnv = async () => {
|
||||
const adminUser = process.env.ADMIN_USER;
|
||||
const adminPass = process.env.ADMIN_PASS;
|
||||
if (!adminUser || !adminPass) {
|
||||
return;
|
||||
}
|
||||
|
||||
const adminEmail = process.env.ADMIN_EMAIL || `${adminUser}@saludut.local`;
|
||||
const adminName = process.env.ADMIN_NAME || 'Administrador Sistema';
|
||||
|
||||
try {
|
||||
const tablesRes = await pool.query(`
|
||||
SELECT
|
||||
to_regclass('public.rol') AS rol,
|
||||
to_regclass('public.usuario') AS usuario
|
||||
`);
|
||||
const tables = tablesRes.rows[0] || {};
|
||||
if (!tables.rol || !tables.usuario) {
|
||||
console.warn('Tablas de usuarios no existen, omitiendo admin automatico.');
|
||||
return;
|
||||
}
|
||||
|
||||
await pool.query(`
|
||||
INSERT INTO rol (nombre_rol, descripcion) VALUES
|
||||
('administrador', 'Puede cargar pacientes, descargar PDFs y ver todas las autorizaciones'),
|
||||
('administrativo_sede', 'Solo puede generar autorizaciones para su sede')
|
||||
ON CONFLICT (nombre_rol) DO NOTHING;
|
||||
`);
|
||||
|
||||
const roleRes = await pool.query(
|
||||
"SELECT id_rol FROM rol WHERE nombre_rol = 'administrador' LIMIT 1"
|
||||
);
|
||||
if (!roleRes.rows[0]) {
|
||||
console.warn('No se encontro el rol administrador.');
|
||||
return;
|
||||
}
|
||||
|
||||
const passwordHash = await bcrypt.hash(adminPass, 10);
|
||||
await pool.query(
|
||||
`
|
||||
INSERT INTO usuario (username, email, password_hash, nombre_completo, id_rol, activo)
|
||||
VALUES ($1, $2, $3, $4, $5, true)
|
||||
ON CONFLICT (username) DO UPDATE SET
|
||||
email = EXCLUDED.email,
|
||||
password_hash = EXCLUDED.password_hash,
|
||||
nombre_completo = EXCLUDED.nombre_completo,
|
||||
id_rol = EXCLUDED.id_rol,
|
||||
activo = true;
|
||||
`,
|
||||
[adminUser, adminEmail, passwordHash, adminName, roleRes.rows[0].id_rol]
|
||||
);
|
||||
|
||||
console.log('Usuario admin asegurado desde .env.');
|
||||
} catch (error) {
|
||||
console.warn('No se pudo crear admin desde .env:', error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// --- 1. DEFINICIÓN DE MIDDLEWARES (ANTES DE USARLOS) ---
|
||||
// Es crucial definir los middlewares aquí para que puedan ser usados por las rutas que se definan más abajo.
|
||||
// Esto resuelve el error: "Cannot access 'verificarToken' before initialization".
|
||||
@ -2211,6 +2269,10 @@ ensureAutorizacionVersionTables().catch((error) => {
|
||||
console.error('Error inicializando tablas de versiones:', error.message);
|
||||
});
|
||||
|
||||
ensureAdminFromEnv().catch((error) => {
|
||||
console.warn('Error asegurando admin desde .env:', error.message);
|
||||
});
|
||||
|
||||
|
||||
// --- 3. INICIO DEL SERVIDOR ---
|
||||
app.listen(PORT, () => {
|
||||
|
||||
7
backend/src/setup.sql
Normal file
7
backend/src/setup.sql
Normal file
@ -0,0 +1,7 @@
|
||||
\\set ON_ERROR_STOP on
|
||||
|
||||
\\i schema.sql
|
||||
\\i cups_schema.sql
|
||||
\\i cups_referencia.sql
|
||||
\\i cups_cubiertos.sql
|
||||
\\i comandos.sql
|
||||
Loading…
Reference in New Issue
Block a user