// server.js
require('dotenv').config();

const express = require('express');
const cors = require('cors');
const { Pool } = require('pg');
const ExcelJS = require('exceljs');
const PDFDocument = require('pdfkit');
const fsPromises = require('fs').promises;
const fs = require('fs');
const { crearLibroAutorizacion } = require('./plantilla_autorizacion');
const {
  crearLibroAutorizacionBrigadasAmbulanciasHospitalarios
} = require('./plantilla_autorizacion_brigadas_ambulancias_hospitalarios');
const path = require('path');
const os = require('os');
const { execFile } = require('child_process');
const multer = require('multer');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
const archiver = require('archiver');
const crypto = require('crypto');

// --- CONFIGURACIÓN INICIAL ---
const JWT_SECRET = process.env.JWT_SECRET || 'secreto-salud-ut-2025';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';

// Multer: guardamos el archivo en memoria
const upload = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 50 * 1024 * 1024, // 50 MB
  },
});

// Path de LibreOffice desde .env
const sofficePath =
  process.env.SOFFICE_PATH || 'C:\\Program Files\\LibreOffice\\program\\soffice.exe';

// Configuración de la Base de Datos
const pool = new Pool({
  host: process.env.DB_HOST || 'localhost',
  user: process.env.DB_USER || 'postgres',
  port: Number(process.env.DB_PORT) || 5432,
  password: process.env.DB_PASSWORD || '1234',
  database: process.env.DB_NAME || 'saludut',
});

const app = express();
const PORT = Number(process.env.PORT) || 3000;

app.use(cors());

// -------------------------
// Jobs en background (memoria)
// -------------------------
const JOB_TTL_MS = 30 * 60 * 1000;
const jobs = new Map();
const jobQueue = [];
let jobProcessing = false;

const createJobId = () =>
  typeof crypto.randomUUID === 'function'
    ? crypto.randomUUID()
    : `${Date.now()}-${Math.random().toString(16).slice(2)}`;

const safeUnlink = async (filePath) => {
  if (!filePath) return;
  try {
    await fsPromises.unlink(filePath);
  } catch (error) {
    if (error.code !== 'ENOENT') {
      console.warn('No se pudo borrar archivo temporal:', error.message);
    }
  }
};

const safeRemoveDir = async (dirPath) => {
  if (!dirPath) return;
  try {
    await fsPromises.rmdir(dirPath);
  } catch (error) {
    if (error.code !== 'ENOENT' && error.code !== 'ENOTEMPTY') {
      console.warn('No se pudo borrar carpeta temporal:', error.message);
    }
  }
};

const sanitizeJob = (job) => {
  if (!job) return null;
  const result = job.result ? { ...job.result } : null;
  if (result?.filePath) {
    delete result.filePath;
  }
  return {
    id: job.id,
    type: job.type,
    status: job.status,
    createdAt: job.createdAt,
    startedAt: job.startedAt,
    finishedAt: job.finishedAt,
    result,
    error: job.error || null,
  };
};

const normalizeSearch = (value) =>
  String(value || '')
    .normalize('NFD')
    .replace(/[\u0300-\u036f]/g, '')
    .toUpperCase()
    .trim();

const scheduleJobCleanup = (jobId) => {
  setTimeout(async () => {
    const job = jobs.get(jobId);
    if (!job) return;
    if (job.result?.filePath) {
      await safeUnlink(job.result.filePath);
      await safeRemoveDir(path.dirname(job.result.filePath));
    }
    jobs.delete(jobId);
  }, JOB_TTL_MS);
};

const runJob = async (job, handler) => {
  job.status = 'running';
  job.startedAt = new Date().toISOString();

  try {
    const result = await handler();
    job.status = 'completed';
    job.finishedAt = new Date().toISOString();
    job.result = result || {};
  } catch (error) {
    console.error('Error en job:', error);
    job.status = 'failed';
    job.finishedAt = new Date().toISOString();
    job.error = { message: error.message || 'Error inesperado' };
  } finally {
    scheduleJobCleanup(job.id);
  }
};

const processQueue = async () => {
  if (jobProcessing) return;
  jobProcessing = true;

  while (jobQueue.length > 0) {
    const { job, handler } = jobQueue.shift();
    await runJob(job, handler);
  }

  jobProcessing = false;
};

const enqueueJob = (job, handler) => {
  jobQueue.push({ job, handler });
  processQueue();
};

// -------------------------
// Inicializar tablas de CUPS
// -------------------------
const ensureCupsTables = async () => {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS cups_referencia (
      codigo VARCHAR(20) PRIMARY KEY,
      descripcion TEXT NOT NULL
    );
  `);

  await pool.query(`
    CREATE TABLE IF NOT EXISTS cups_cubiertos (
      codigo VARCHAR(20) PRIMARY KEY,
      descripcion TEXT NOT NULL,
      descripcion_busqueda TEXT,
      nivel VARCHAR(10),
      especialidad TEXT,
      activo BOOLEAN NOT NULL DEFAULT true
    );
  `);

  await pool.query(`
    ALTER TABLE autorizacion
    ADD COLUMN IF NOT EXISTS cup_codigo VARCHAR(20);
  `);

  await pool.query(`
    ALTER TABLE autorizacion
    ADD COLUMN IF NOT EXISTS tipo_autorizacion VARCHAR(50) NOT NULL DEFAULT 'consultas_externas';
  `);

  await pool.query(`
    ALTER TABLE autorizacion
    ADD COLUMN IF NOT EXISTS tipo_servicio VARCHAR(50);
  `);

  await pool.query(`
    ALTER TABLE autorizacion
    ADD COLUMN IF NOT EXISTS version INTEGER NOT NULL DEFAULT 1;
  `);

  await pool.query(`
    UPDATE autorizacion SET version = 1 WHERE version IS NULL;
  `);

  await pool.query(`
    ALTER TABLE cups_cubiertos
    ADD COLUMN IF NOT EXISTS activo BOOLEAN NOT NULL DEFAULT true;
  `);

  await pool.query(`
    ALTER TABLE cups_cubiertos
    ADD COLUMN IF NOT EXISTS descripcion_busqueda TEXT;
  `);

  await pool.query(`
    ALTER TABLE cups_cubiertos
    ADD COLUMN IF NOT EXISTS nivel VARCHAR(10);
  `);

  await pool.query(`
    ALTER TABLE cups_cubiertos
    ADD COLUMN IF NOT EXISTS especialidad TEXT;
  `);

  await pool.query(`
    ALTER TABLE autorizacion
    DROP CONSTRAINT IF EXISTS autorizacion_cup_codigo_fk;
  `);
};

const ensureAutorizacionVersionTables = async () => {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS autorizacion_version (
      id SERIAL PRIMARY KEY,
      numero_autorizacion VARCHAR(50) NOT NULL,
      version INTEGER NOT NULL,
      id_ips INTEGER NOT NULL,
      numero_documento_autorizante BIGINT NOT NULL,
      fecha_autorizacion DATE,
      observacion TEXT,
      cup_codigo VARCHAR(20),
      tipo_autorizacion VARCHAR(50),
      tipo_servicio VARCHAR(50),
      fecha_version TIMESTAMP NOT NULL DEFAULT NOW()
    );
  `);

  await pool.query(`
    CREATE UNIQUE INDEX IF NOT EXISTS autorizacion_version_unique
    ON autorizacion_version (numero_autorizacion, version);
  `);
};

const ensureIpsConvenio = async () => {
  await pool.query(`
    ALTER TABLE ips
    ADD COLUMN IF NOT EXISTS tiene_convenio BOOLEAN NOT NULL DEFAULT true;
  `);

  await pool.query(`
    UPDATE ips
    SET tiene_convenio = true
    WHERE tiene_convenio IS NULL;
  `);
};

const ensureProfesionalRepsTable = async () => {
  await pool.query(`
    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 NOT NULL DEFAULT true
    );
  `);

  await pool.query(`
    ALTER TABLE profesional_reps
    ADD COLUMN IF NOT EXISTS activo BOOLEAN NOT NULL DEFAULT true;
  `);

  await pool.query(`
    UPDATE profesional_reps
    SET activo = true
    WHERE activo IS NULL;
  `);
};

const ensureAutorizacionEstado = async () => {
  await pool.query(`
    ALTER TABLE autorizacion
    ADD COLUMN IF NOT EXISTS estado_autorizacion VARCHAR(20) NOT NULL DEFAULT 'pendiente';
  `);

  await pool.query(`
    UPDATE autorizacion
    SET estado_autorizacion = 'pendiente'
    WHERE estado_autorizacion IS NULL;
  `);

  await pool.query(`
    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 $$;
  `);
};

const ensureUsuarioTokenVersion = async () => {
  await pool.query(`
    ALTER TABLE usuario
    ADD COLUMN IF NOT EXISTS token_version INTEGER NOT NULL DEFAULT 1;
  `);

  await pool.query(`
    UPDATE usuario
    SET token_version = 1
    WHERE token_version IS NULL;
  `);
};

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".

// Middleware para verificar token JWT
const verificarToken = async (req, res, next) => {
  const token = req.headers['authorization']?.replace('Bearer ', '');
  
  if (!token) {
    return res.status(401).json({ error: 'Token no proporcionado' });
  }

  try {
    const decoded = jwt.verify(token, JWT_SECRET);
    const { rows } = await pool.query(
      'SELECT id_usuario, activo, token_version FROM usuario WHERE id_usuario = $1',
      [decoded.id_usuario]
    );

    if (rows.length === 0) {
      return res.status(401).json({ error: 'Usuario no encontrado' });
    }

    const usuarioDb = rows[0];
    if (!usuarioDb.activo) {
      return res.status(401).json({ error: 'Usuario inactivo' });
    }

    if (Number(usuarioDb.token_version) !== Number(decoded.token_version)) {
      return res.status(401).json({ error: 'Sesion expirada' });
    }

    req.usuario = decoded;
    return next();
  } catch (error) {
    console.error('Error validando token:', error.message);
    return res.status(401).json({ error: 'Token invalido o expirado' });
  }
};

// Middleware para verificar rol específico
const verificarRol = (rolPermitido) => {
  return (req, res, next) => {
    if (req.usuario.nombre_rol !== rolPermitido) {
      return res.status(403).json({ error: 'No tienes permisos para esta acción' });
    }
    next();
  };
};

// Middleware para verificar si es administrador
const esAdministrador = verificarRol('administrador');

// Middleware para verificar si puede generar autorizaciones
const puedeGenerarAutorizaciones = (req, res, next) => {
  if (req.usuario.nombre_rol !== 'administrador' && req.usuario.nombre_rol !== 'administrativo_sede') {
    return res.status(403).json({ error: 'No tienes permisos para generar autorizaciones' });
  }
  next();
};

const puedeDescargarPdfAutorizacion = async (req, res, next) => {
  if (req.usuario.nombre_rol === 'administrador') {
    return next();
  }

  const numeroAutorizacion =
    req.body?.numero_autorizacion || req.query?.numero_autorizacion;

  if (!numeroAutorizacion) {
    return res.status(400).json({ error: 'Falta parametro numero_autorizacion' });
  }

  try {
    const { rows } = await pool.query(
      `
      SELECT COALESCE(estado_autorizacion, 'pendiente') AS estado_autorizacion
      FROM autorizacion
      WHERE numero_autorizacion = $1
      `,
      [numeroAutorizacion]
    );

    if (rows.length === 0) {
      return res.status(404).json({ error: 'Autorizacion no encontrada' });
    }

    if (rows[0].estado_autorizacion !== 'autorizado') {
      return res.status(403).json({
        error: 'Autorizacion pendiente o no autorizada',
      });
    }

    return next();
  } catch (error) {
    console.error('Error validando estado de autorizacion:', error.message);
    return res.status(500).json({ error: 'Error validando autorizacion' });
  }
};

// Middleware para verificar acceso por sede
const verificarAccesoSede = async (req, res, next) => {
  // Si es administrador, tiene acceso a todo
  if (req.usuario.nombre_rol === 'administrador') {
    return next();
  }

  // Si es administrativo, verificar que tenga acceso a la sede del paciente
  const { interno } = req.query;
  if (!interno) {
    return res.status(400).json({ error: 'Se requiere el parámetro interno' });
  }

  try {
    const sql = `
      SELECT 1 FROM usuario_sede us
      JOIN ingreso i ON us.codigo_establecimiento = i.codigo_establecimiento
      WHERE us.id_usuario = $1 AND i.interno = $2
    `;
    
    const { rows } = await pool.query(sql, [req.usuario.id_usuario, interno]);
    
    if (rows.length === 0) {
      return res.status(403).json({ error: 'No tienes permisos para acceder a este interno' });
    }

    next();
  } catch (error) {
    console.error('Error verificando acceso a sede:', error);
    return res.status(500).json({ error: 'Error en el servidor' });
  }
};

// --- 2. DEFINICIÓN DE RUTAS ---

// RUTA ESPECIAL PARA SUBIDA DE ARCHIVOS
// ¡IMPORTANTE! Esta ruta usa 'multer' y DEBE estar definida ANTES de los middlewares `express.json()` y `express.urlencoded()`.
// Esto resuelve el error: "SyntaxError: Unexpected token '-', "------WebK"... is not valid JSON".
app.post(
  '/api/cargar-excel-pacientes',
  verificarToken,
  esAdministrador,
  upload.single('archivo'),
  async (req, res) => {
    try {
      if (!req.file) {
        return res.status(400).json({ error: 'No se recibió archivo Excel' });
      }

      const jobId = createJobId();
      const job = {
        id: jobId,
        type: 'excel-carga',
        status: 'queued',
        createdAt: new Date().toISOString(),
        ownerId: req.usuario.id_usuario,
      };

      jobs.set(jobId, job);

      const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
      await fsPromises.mkdir(jobTmpDir, { recursive: true });
      const inputPath = path.join(jobTmpDir, `pacientes_${jobId}.xlsx`);
      await fsPromises.writeFile(inputPath, req.file.buffer);

      enqueueJob(job, async () => {
        try {
          return await procesarExcelPacientes(inputPath);
        } finally {
          await safeUnlink(inputPath);
          await safeRemoveDir(jobTmpDir);
        }
      });

      return res.status(202).json(sanitizeJob(job));
    } catch (error) {
      console.error('Error creando job de Excel:', error);
      return res.status(500).json({
        error: 'Error creando el job para procesar el Excel',
      });
    }
  }
);

app.post(
  '/api/cargar-cups',
  verificarToken,
  esAdministrador,
  upload.fields([
    { name: 'nota_tecnica', maxCount: 1 },
    { name: 'tabla_referencia', maxCount: 1 },
  ]),
  async (req, res) => {
    try {
      const notaFile = req.files?.nota_tecnica?.[0];
      const referenciaFile = req.files?.tabla_referencia?.[0];

      if (!notaFile || !referenciaFile) {
        return res.status(400).json({
          error: 'Debes enviar nota_tecnica y tabla_referencia en la solicitud.',
        });
      }

      const jobId = createJobId();
      const job = {
        id: jobId,
        type: 'cups-carga',
        status: 'queued',
        createdAt: new Date().toISOString(),
        ownerId: req.usuario.id_usuario,
      };

      jobs.set(jobId, job);

      const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
      await fsPromises.mkdir(jobTmpDir, { recursive: true });
      const notaPath = path.join(jobTmpDir, `nota_tecnica_${jobId}.xlsx`);
      const referenciaPath = path.join(jobTmpDir, `tabla_referencia_${jobId}.xlsx`);
      await fsPromises.writeFile(notaPath, notaFile.buffer);
      await fsPromises.writeFile(referenciaPath, referenciaFile.buffer);

      enqueueJob(job, async () => {
        try {
          return await procesarExcelCups(notaPath, referenciaPath);
        } finally {
          await safeUnlink(notaPath);
          await safeUnlink(referenciaPath);
          await safeRemoveDir(jobTmpDir);
        }
      });

      return res.status(202).json(sanitizeJob(job));
    } catch (error) {
      console.error('Error creando job de CUPS:', error);
      return res.status(500).json({
        error: 'Error creando el job para procesar los CUPS',
      });
    }
  }
);

app.post(
  '/api/cargar-ips',
  verificarToken,
  esAdministrador,
  upload.single('archivo'),
  async (req, res) => {
    try {
      if (!req.file) {
        return res.status(400).json({ error: 'No se recibio archivo Excel' });
      }

      const jobId = createJobId();
      const job = {
        id: jobId,
        type: 'ips-carga',
        status: 'queued',
        createdAt: new Date().toISOString(),
        ownerId: req.usuario.id_usuario,
      };

      jobs.set(jobId, job);

      const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
      await fsPromises.mkdir(jobTmpDir, { recursive: true });
      const inputPath = path.join(jobTmpDir, `ips_${jobId}.xlsx`);
      await fsPromises.writeFile(inputPath, req.file.buffer);

      enqueueJob(job, async () => {
        try {
          return await procesarExcelIps(inputPath);
        } finally {
          await safeUnlink(inputPath);
          await safeRemoveDir(jobTmpDir);
        }
      });

      return res.status(202).json(sanitizeJob(job));
    } catch (error) {
      console.error('Error creando job de IPS:', error);
      return res.status(500).json({
        error: 'Error creando el job para procesar el Excel de IPS',
      });
    }
  }
);

app.post(
  '/api/cargar-reps',
  verificarToken,
  esAdministrador,
  upload.single('archivo'),
  async (req, res) => {
    try {
      if (!req.file) {
        return res.status(400).json({ error: 'No se recibio archivo Excel' });
      }

      const jobId = createJobId();
      const job = {
        id: jobId,
        type: 'reps-carga',
        status: 'queued',
        createdAt: new Date().toISOString(),
        ownerId: req.usuario.id_usuario,
      };

      jobs.set(jobId, job);

      const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
      await fsPromises.mkdir(jobTmpDir, { recursive: true });
      const inputPath = path.join(jobTmpDir, `reps_${jobId}.xlsx`);
      await fsPromises.writeFile(inputPath, req.file.buffer);

      enqueueJob(job, async () => {
        try {
          return await procesarExcelReps(inputPath);
        } finally {
          await safeUnlink(inputPath);
          await safeRemoveDir(jobTmpDir);
        }
      });

      return res.status(202).json(sanitizeJob(job));
    } catch (error) {
      console.error('Error creando job de REPS:', error);
      return res.status(500).json({
        error: 'Error creando el job para procesar el Excel de REPS',
      });
    }
  }
);

app.post(
  '/api/cargar-autorizaciones-masivas',
  verificarToken,
  upload.single('archivo'),
  async (req, res) => {
    try {
      if (!req.file) {
        return res.status(400).json({ error: 'No se recibio archivo Excel' });
      }

      const jobId = createJobId();
      const job = {
        id: jobId,
        type: 'autorizaciones-masivas',
        status: 'queued',
        createdAt: new Date().toISOString(),
        ownerId: req.usuario.id_usuario,
      };

      jobs.set(jobId, job);

      const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
      await fsPromises.mkdir(jobTmpDir, { recursive: true });
      const inputPath = path.join(jobTmpDir, `autorizaciones_${jobId}.xlsx`);
      await fsPromises.writeFile(inputPath, req.file.buffer);

      enqueueJob(job, async () => {
        try {
          return await procesarExcelAutorizacionesMasivas(inputPath, req.usuario);
        } finally {
          await safeUnlink(inputPath);
          await safeRemoveDir(jobTmpDir);
        }
      });

      return res.status(202).json(sanitizeJob(job));
    } catch (error) {
      console.error('Error creando job de autorizaciones masivas:', error);
      return res.status(500).json({
        error: 'Error creando el job para procesar autorizaciones masivas',
      });
    }
  }
);

// MIDDLEWARES GLOBALES (para el resto de las rutas que sí usan JSON)
app.use(
  express.json({
    limit: '50mb',
  })
);

app.use(
  express.urlencoded({
    extended: true,
    limit: '50mb',
  })
);

// -------------------------
// Helpers de procesamiento pesado
// -------------------------
async function procesarExcelPacientes(inputFilePath) {
  const backendDir = __dirname;
  const excelPath = path.join(backendDir, 'pacientes.xlsx');
  const scriptPath = path.join(backendDir, 'pacientes.py');

  await fsPromises.copyFile(inputFilePath, excelPath);

  await new Promise((resolve, reject) => {
    execFile(
      process.env.PYTHON_PATH || 'python',
      [scriptPath],
      { cwd: backendDir },
      (error, stdout, stderr) => {
        console.log('pacientes.py stdout:\n', stdout);
        console.error('pacientes.py stderr:\n', stderr);
        if (error) return reject(error);
        resolve();
      }
    );
  });

  const files = ['establecimiento.sql', 'paciente.sql', 'ingreso.sql'];
  console.log('Ejecutando scripts SQL generados...');

  await pool.query('BEGIN');
  try {
    for (const fname of files) {
      const fullPath = path.join(backendDir, fname);
      const sqlText = await fsPromises.readFile(fullPath, 'utf8');

      const lines = sqlText.split('\n');
      for (const line of lines) {
        const stmt = line.trim();
        if (!stmt || stmt.startsWith('--')) continue;
        await pool.query(stmt);
      }
    }

    await pool.query('COMMIT');
  } catch (error) {
    await pool.query('ROLLBACK');
    throw error;
  }

  let activos = null;
  let antiguos = null;

  try {
    const activosRes = await pool.query(
      'SELECT COUNT(*)::int AS c FROM paciente WHERE activo = true'
    );
    const antiguosRes = await pool.query(
      'SELECT COUNT(*)::int AS c FROM paciente WHERE activo = false'
    );
    activos = activosRes.rows[0].c;
    antiguos = antiguosRes.rows[0].c;
  } catch (error) {
    console.warn(
      'No se pudieron obtener stats de activos/antiguos (¿columna activo existe?):',
      error.message
    );
  }

  return {
    ok: true,
    mensaje: 'Excel procesado y BD actualizada',
    activos,
    antiguos,
  };
}

async function ejecutarSqlFile(filePath) {
  const sqlText = await fsPromises.readFile(filePath, 'utf8');
  const lines = sqlText.split('\n');
  for (const line of lines) {
    const stmt = line.trim();
    if (!stmt || stmt.startsWith('--')) continue;
    await pool.query(stmt);
  }
}

async function procesarExcelCups(notaPath, referenciaPath) {
  await ensureCupsTables();

  const backendDir = __dirname;
  const notaDestino = path.join(
    backendDir,
    'NOTA TECNICA PPL AJUSTADA. (1) 1.xlsx'
  );
  const referenciaDestino = path.join(
    backendDir,
    'TablaReferencia_CUPS__1-2024 (1).xlsx'
  );
  const scriptPath = path.join(backendDir, 'cargar_cups.py');

  await fsPromises.copyFile(notaPath, notaDestino);
  await fsPromises.copyFile(referenciaPath, referenciaDestino);

  await new Promise((resolve, reject) => {
    execFile(
      process.env.PYTHON_PATH || 'python',
      [scriptPath],
      { cwd: backendDir },
      (error, stdout, stderr) => {
        console.log('cargar_cups.py stdout:\n', stdout);
        console.error('cargar_cups.py stderr:\n', stderr);
        if (error) return reject(error);
        resolve();
      }
    );
  });

  const files = ['cups_referencia.sql', 'cups_cubiertos.sql'];
  console.log('Ejecutando scripts SQL de CUPS...');

  await pool.query('BEGIN');
  try {
    for (const fname of files) {
      const fullPath = path.join(backendDir, fname);
      await ejecutarSqlFile(fullPath);
    }
    await pool.query('COMMIT');
  } catch (error) {
    await pool.query('ROLLBACK');
    throw error;
  }

  const referenciaRes = await pool.query(
    'SELECT COUNT(*)::int AS total FROM cups_referencia'
  );
  const cubiertosRes = await pool.query(
    'SELECT COUNT(*)::int AS total FROM cups_cubiertos WHERE activo = true'
  );

  return {
    ok: true,
    mensaje: 'CUPS cargados correctamente',
    referencia: referenciaRes.rows[0]?.total ?? null,
    cubiertosActivos: cubiertosRes.rows[0]?.total ?? null,
  };
}



const getExcelCellText = (value) => {
  if (value === null || value === undefined) return '';
  if (typeof value === 'string') return value.trim();
  if (typeof value === 'number') return String(value);
  if (value instanceof Date) return value.toISOString().slice(0, 10);
  if (typeof value === 'object') {
    if (typeof value.text === 'string') return value.text.trim();
    if (Array.isArray(value.richText)) {
      return value.richText.map((rt) => rt.text || '').join('').trim();
    }
    if (value.result !== undefined) return String(value.result).trim();
    if (value.formula && value.result !== undefined) return String(value.result).trim();
    if (typeof value.hyperlink === 'string' && typeof value.text === 'string') {
      return value.text.trim();
    }
  }
  return String(value).trim();
};

const normalizeHeader = (value) =>
  normalizeSearch(value).replace(/[^A-Z0-9]/g, '');

const normalizeDigits = (value) => String(value || '').replace(/\D/g, '');

const normalizeNameKey = (value) =>
  normalizeSearch(value).replace(/[^A-Z0-9]/g, '');

const extractCupCodigo = (value) => {
  const text = String(value || '').trim();
  if (!text) return '';
  const match = text.match(/\d{4,10}/);
  if (match) return match[0];
  const digits = normalizeDigits(text);
  return digits || '';
};

const parseServicio = (value) => {
  const raw = normalizeSearch(value);
  if (raw.includes('AMBULANCIA')) {
    return { tipo_autorizacion: 'brigadas_ambulancias_hospitalarios', tipo_servicio: 'ambulancias' };
  }
  if (raw.includes('HOSPITAL')) {
    return { tipo_autorizacion: 'brigadas_ambulancias_hospitalarios', tipo_servicio: 'hospitalarios' };
  }
  if (raw.includes('BRIGADA')) {
    return { tipo_autorizacion: 'brigadas_ambulancias_hospitalarios', tipo_servicio: 'brigadas' };
  }
  return { tipo_autorizacion: 'consultas_externas', tipo_servicio: null };
};

const splitNombrePaciente = (nombreCompleto) => {
  const limpio = String(nombreCompleto || '').trim();
  if (!limpio) {
    return {
      primer_nombre: null,
      segundo_nombre: null,
      primer_apellido: null,
      segundo_apellido: null,
    };
  }

  const partes = limpio.split(/\s+/);
  if (partes.length === 1) {
    return {
      primer_nombre: partes[0],
      segundo_nombre: null,
      primer_apellido: null,
      segundo_apellido: null,
    };
  }

  if (partes.length === 2) {
    return {
      primer_nombre: partes[0],
      segundo_nombre: null,
      primer_apellido: partes[1],
      segundo_apellido: null,
    };
  }

  const apellidos = partes.slice(-2);
  const nombres = partes.slice(0, -2);
  return {
    primer_nombre: nombres[0] || null,
    segundo_nombre: nombres.slice(1).join(' ') || null,
    primer_apellido: apellidos[0] || null,
    segundo_apellido: apellidos[1] || null,
  };
};

async function procesarExcelIps(inputFilePath) {
  await ensureIpsConvenio();

  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(inputFilePath);
  const sheet = workbook.worksheets[0];

  if (!sheet) {
    throw new Error('No se encontro una hoja en el Excel de IPS');
  }

  const headerRow = sheet.getRow(1);
  const headers = {};
  headerRow.eachCell((cell, colNumber) => {
    const base = normalizeHeader(getExcelCellText(cell.value));
    if (!base) return;
    let key = base;
    if (headers[key]) {
      let idx = 1;
      while (headers[`${base}${idx}`]) {
        idx += 1;
      }
      key = `${base}${idx}`;
    }
    headers[key] = colNumber;
  });

  const getValue = (row, key) => {
    const col = headers[key];
    if (!col) return '';
    return getExcelCellText(row.getCell(col).value);
  };

  const resumen = {
    total: 0,
    insertados: 0,
    actualizados: 0,
    omitidos: 0,
    desactivados: 0,
    errores: [],
  };

  const nitKeys = new Set();
  const codigoKeys = new Set();
  const nombreKeys = new Set();

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    for (let i = 2; i <= sheet.rowCount; i++) {
      const row = sheet.getRow(i);
      const nit = getValue(row, 'NIT');
      const nombre = getValue(row, 'PRESTADOR');

      if (!nit && !nombre) {
        continue;
      }

      resumen.total += 1;

      const direccion = getValue(row, 'DIRECCION');
      const departamento = getValue(row, 'DEPARTAMENTO');
      const municipio = getValue(row, 'MUNICIPIO');
      const telefono = getValue(row, 'TELEFONO');
      const codigoIps = getValue(row, 'CODIGOIPS');

      const nitDigits = normalizeDigits(nit);
      const codigoDigits = normalizeDigits(codigoIps);
      const lookupKey = nitDigits || codigoDigits;
      const nombreKey = normalizeNameKey(nombre);

      if (nitDigits) nitKeys.add(nitDigits);
      if (codigoDigits) codigoKeys.add(codigoDigits);
      if (nombreKey) nombreKeys.add(nombreKey);

      let existente = null;
      if (lookupKey) {
        const res = await client.query(
          `
          SELECT id_ips
          FROM ips
          WHERE regexp_replace(nit, '\\D', '', 'g') = $1
            OR regexp_replace(codigo_ips, '\\D', '', 'g') = $1
          LIMIT 1
          `,
          [lookupKey]
        );
        existente = res.rows[0] || null;
      }

      if (!existente && nombreKey) {
        const res = await client.query(
          `
          SELECT id_ips
          FROM ips
          WHERE regexp_replace(
                  translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
                  '[^A-Z0-9]',
                  '',
                  'g'
                ) = $1
          LIMIT 1
          `,
          [nombreKey]
        );
        existente = res.rows[0] || null;
      }

      if (existente) {
        await client.query(
          `
          UPDATE ips
          SET nombre_ips = COALESCE($1, nombre_ips),
              codigo_ips = COALESCE($2, codigo_ips),
              direccion = COALESCE($3, direccion),
              telefono = COALESCE($4, telefono),
              departamento = COALESCE($5, departamento),
              municipio = COALESCE($6, municipio),
              tiene_convenio = true
          WHERE id_ips = $7
          `,
          [
            nombre || null,
            codigoIps || null,
            direccion || null,
            telefono || null,
            departamento || null,
            municipio || null,
            existente.id_ips,
          ]
        );
        resumen.actualizados += 1;
      } else {
        await client.query(
          `
          INSERT INTO ips
            (nit, nombre_ips, codigo_ips, direccion, telefono, departamento, municipio, tiene_convenio)
          VALUES ($1, $2, $3, $4, $5, $6, $7, true)
          `,
          [
            nit || null,
            nombre || null,
            codigoIps || null,
            direccion || null,
            telefono || null,
            departamento || null,
            municipio || null,
          ]
        );
        resumen.insertados += 1;
      }
    }

    if (resumen.total > 0) {
      const nitList = Array.from(nitKeys);
      const codigoList = Array.from(codigoKeys);
      const nombreList = Array.from(nombreKeys);

      const desactRes = await client.query(
        `
        UPDATE ips
        SET tiene_convenio = false
        WHERE NOT (
          (regexp_replace(nit, '\\D', '', 'g') <> '' AND regexp_replace(nit, '\\D', '', 'g') = ANY($1::text[]))
          OR (regexp_replace(codigo_ips, '\\D', '', 'g') <> '' AND regexp_replace(codigo_ips, '\\D', '', 'g') = ANY($2::text[]))
          OR (
            regexp_replace(
              translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
              '[^A-Z0-9]',
              '',
              'g'
            ) <> ''
            AND regexp_replace(
              translate(UPPER(nombre_ips), 'ÁÉÍÓÚÜÑ', 'AEIOUUN'),
              '[^A-Z0-9]',
              '',
              'g'
            ) = ANY($3::text[])
          )
        )
        AND tiene_convenio IS DISTINCT FROM false
        `,
        [nitList, codigoList, nombreList]
      );

      resumen.desactivados = desactRes.rowCount || 0;
    }

    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }

  return {
    ok: true,
    mensaje: 'IPS cargadas correctamente',
    ...resumen,
  };
}

async function procesarExcelReps(inputFilePath) {
  await ensureProfesionalRepsTable();

  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(inputFilePath);
  const sheet = workbook.worksheets[0];

  if (!sheet) {
    throw new Error('No se encontro una hoja en el Excel de REPS');
  }

  const headerRow = sheet.getRow(1);
  const headers = {};
  headerRow.eachCell((cell, colNumber) => {
    headers[normalizeHeader(getExcelCellText(cell.value))] = colNumber;
  });

  const getValue = (row, key) => {
    const col = headers[key];
    if (!col) return '';
    return getExcelCellText(row.getCell(col).value);
  };

  const resumen = {
    total: 0,
    insertados: 0,
    actualizados: 0,
    omitidos: 0,
    desactivados: 0,
  };

  const pares = new Set();

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    for (let i = 2; i <= sheet.rowCount; i++) {
      const row = sheet.getRow(i);
      const nit = getValue(row, 'NITSNIT');
      const nombre = getValue(row, 'RAZONSOCIAL');
      const codigoHabilitacion = getValue(row, 'CODIGOHABILITACION');

      if (!nit && !nombre && !codigoHabilitacion) {
        continue;
      }

      resumen.total += 1;

      const direccion = getValue(row, 'DIRECCION');
      const telefono = getValue(row, 'TELEFONO');
      const departamento = getValue(row, 'DEPANOMBRE');
      const municipio = getValue(row, 'MUNINOMBRE');

      const nitKey = normalizeDigits(nit);
      const codigoKey = normalizeDigits(codigoHabilitacion);
      if (nitKey && codigoKey) {
        pares.add(`${nitKey}||${codigoKey}`);
      }

      let existente = null;
      if (nitKey && codigoKey) {
        const res = await client.query(
          `
          SELECT id
          FROM profesional_reps
          WHERE regexp_replace(nit, '\\D', '', 'g') = $1
            AND regexp_replace(codigo_habilitacion, '\\D', '', 'g') = $2
          LIMIT 1
          `,
          [nitKey, codigoKey]
        );
        existente = res.rows[0] || null;
      }

      if (existente) {
        await client.query(
          `
          UPDATE profesional_reps
          SET nombre_profesional = COALESCE($1, nombre_profesional),
              direccion = COALESCE($2, direccion),
              telefono = COALESCE($3, telefono),
              departamento = COALESCE($4, departamento),
              municipio = COALESCE($5, municipio),
              activo = true
          WHERE id = $6
          `,
          [
            nombre || null,
            direccion || null,
            telefono || null,
            departamento || null,
            municipio || null,
            existente.id,
          ]
        );
        resumen.actualizados += 1;
      } else {
        await client.query(
          `
          INSERT INTO profesional_reps
            (nit, nombre_profesional, codigo_habilitacion, direccion, telefono, departamento, municipio, activo)
          VALUES ($1, $2, $3, $4, $5, $6, $7, true)
          `,
          [
            nit || null,
            nombre || null,
            codigoHabilitacion || null,
            direccion || null,
            telefono || null,
            departamento || null,
            municipio || null,
          ]
        );
        resumen.insertados += 1;
      }
    }

    const paresLista = Array.from(pares);
    if (resumen.total > 0 && paresLista.length > 0) {
      const nitList = paresLista.map((p) => p.split('||')[0]);
      const codigoList = paresLista.map((p) => p.split('||')[1]);

      const desactRes = await client.query(
        `
        UPDATE profesional_reps
        SET activo = false
        WHERE NOT EXISTS (
          SELECT 1
          FROM UNNEST($1::text[], $2::text[]) AS x(nit, codigo)
          WHERE regexp_replace(profesional_reps.nit, '\\D', '', 'g') = x.nit
            AND regexp_replace(profesional_reps.codigo_habilitacion, '\\D', '', 'g') = x.codigo
        )
        AND activo IS DISTINCT FROM false
        `,
        [nitList, codigoList]
      );

      resumen.desactivados = desactRes.rowCount || 0;
    }

    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }

  return {
    ok: true,
    mensaje: 'REPS cargados correctamente',
    ...resumen,
  };
}

async function procesarExcelAutorizacionesMasivas(inputFilePath, usuario) {
  const workbook = new ExcelJS.Workbook();
  await workbook.xlsx.readFile(inputFilePath);
  const sheet = workbook.worksheets[0];

  if (!sheet) {
    throw new Error('No se encontro una hoja en el Excel de autorizaciones');
  }

  const headerRow = sheet.getRow(1);
  const headers = {};
  headerRow.eachCell((cell, colNumber) => {
    headers[normalizeHeader(getExcelCellText(cell.value))] = colNumber;
  });

  const getValue = (row, key) => {
    const col = headers[key];
    if (!col) return '';
    return getExcelCellText(row.getCell(col).value);
  };

  const autorizanteRes = await pool.query(
    `
    SELECT numero_documento
    FROM autorizante
    WHERE UPPER(nombre) LIKE '%CRISTIAN%YARA%'
    LIMIT 1
    `
  );

  if (autorizanteRes.rows.length === 0) {
    throw new Error('No se encontro el autorizante CRISTIAN YARA en la base de datos');
  }

  const autorizanteDefault = autorizanteRes.rows[0].numero_documento;
  const resumen = {
    total: 0,
    creadas: 0,
    omitidas: 0,
    duplicados: 0,
    sin_paciente: 0,
    sin_cups: 0,
    sin_ips: 0,
    cups_no_cubiertos: 0,
    ips_sin_convenio: 0,
    errores: [],
  };

  const cupCache = new Map();
  const pacienteCache = new Map();
  const ipsCache = new Map();
  const establecimientoCache = new Map();

  const getEstablecimientoInfo = async (interno) => {
    if (establecimientoCache.has(interno)) {
      return establecimientoCache.get(interno);
    }
    const res = await client.query(
      `
      SELECT e.epc_departamento, e.epc_ciudad
      FROM ingreso i
      JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento
      WHERE i.interno = $1
      LIMIT 1
      `,
      [interno]
    );
    const info = res.rows[0]
      ? {
          departamento: res.rows[0].epc_departamento || null,
          municipio: res.rows[0].epc_ciudad || null,
        }
      : null;
    establecimientoCache.set(interno, info);
    return info;
  };

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    for (let i = 2; i <= sheet.rowCount; i++) {
      const row = sheet.getRow(i);
      const cedula = getValue(row, 'CEDULA');
      const procedimiento = getValue(row, 'PROCEDIMIENTOQUESEAUTORIZA');

      if (!cedula && !procedimiento) {
        continue;
      }

      resumen.total += 1;

      if (!cedula) {
        resumen.omitidas += 1;
        resumen.sin_paciente += 1;
        if (resumen.errores.length < 50) {
          resumen.errores.push({ fila: i, error: 'Cedula vacia' });
        }
        continue;
      }

      let interno = pacienteCache.get(cedula);
      if (!interno) {
        const pacienteRes = await client.query(
          `
          SELECT interno
          FROM paciente
          WHERE interno = $1 OR numero_documento = $1
          LIMIT 1
          `,
          [cedula]
        );
        interno = pacienteRes.rows[0]?.interno || null;
        if (interno) {
          pacienteCache.set(cedula, interno);
        }
      }

      if (!interno) {
        resumen.omitidas += 1;
        resumen.sin_paciente += 1;
        if (resumen.errores.length < 50) {
          resumen.errores.push({ fila: i, error: `Paciente no encontrado: ${cedula}` });
        }
        continue;
      }

      const cupCodigo = extractCupCodigo(procedimiento);
      if (!cupCodigo) {
        resumen.omitidas += 1;
        resumen.sin_cups += 1;
        if (resumen.errores.length < 50) {
          resumen.errores.push({ fila: i, error: 'No se pudo obtener CUPS' });
        }
        continue;
      }

      let cupCubierto = cupCache.get(cupCodigo);
      if (cupCubierto === undefined) {
        const cupRes = await client.query(
          'SELECT 1 FROM cups_cubiertos WHERE codigo = $1 AND activo = true',
          [cupCodigo]
        );
        cupCubierto = cupRes.rows.length > 0;
        cupCache.set(cupCodigo, cupCubierto);
      }

      if (!cupCubierto) {
        resumen.cups_no_cubiertos += 1;
      }

      const nitIps = getValue(row, 'NITIPS');
      const hospital = getValue(row, 'HOSPITALCLINICA');
      const departamentoExcel =
        getValue(row, 'DEPARTAMENTO') ||
        getValue(row, 'BOGOTA') ||
        getValue(row, 'BOGOTA1');
      const municipioExcel =
        getValue(row, 'MUNICIPIO') ||
        getValue(row, 'BOGOTA1') ||
        getValue(row, 'BOGOTA');

      let departamento = departamentoExcel;
      let municipio = municipioExcel;
      if (!departamento || !municipio) {
        const estInfo = await getEstablecimientoInfo(interno);
        if (!departamento) {
          departamento = estInfo?.departamento || '';
        }
        if (!municipio) {
          municipio = estInfo?.municipio || '';
        }
      }

      const ipsKey = normalizeDigits(nitIps) || normalizeSearch(hospital);
      let ipsInfo = ipsCache.get(ipsKey);

      if (!ipsInfo) {
        const nitDigits = normalizeDigits(nitIps);
        let ipsRes = null;
        if (nitDigits) {
          ipsRes = await client.query(
            `
            SELECT id_ips, nombre_ips, tiene_convenio, departamento, municipio
            FROM ips
            WHERE regexp_replace(nit, '\\D', '', 'g') = $1
               OR regexp_replace(codigo_ips, '\\D', '', 'g') = $1
            LIMIT 1
            `,
            [nitDigits]
          );
        }

        if (!ipsRes || ipsRes.rows.length === 0) {
          if (hospital) {
            ipsRes = await client.query(
              `
              SELECT id_ips, nombre_ips, tiene_convenio, departamento, municipio
              FROM ips
              WHERE UPPER(nombre_ips) = $1
              LIMIT 1
              `,
              [normalizeSearch(hospital)]
            );
          }
        }

        if (ipsRes && ipsRes.rows.length > 0) {
          const ipsRow = ipsRes.rows[0];
          if (
            (departamento && !ipsRow.departamento) ||
            (municipio && !ipsRow.municipio)
          ) {
            await client.query(
              `
              UPDATE ips
              SET departamento = COALESCE(departamento, $1),
                  municipio = COALESCE(municipio, $2)
              WHERE id_ips = $3
              `,
              [departamento || null, municipio || null, ipsRow.id_ips]
            );
          }
          ipsInfo = {
            id_ips: ipsRow.id_ips,
            nombre_ips: ipsRow.nombre_ips,
            tiene_convenio: ipsRow.tiene_convenio !== false,
            departamento: ipsRow.departamento || departamento || null,
            municipio: ipsRow.municipio || municipio || null,
          };
        } else if (nitIps || hospital) {
          const insertRes = await client.query(
            `
            INSERT INTO ips
              (nit, nombre_ips, codigo_ips, direccion, telefono, departamento, municipio, tiene_convenio)
            VALUES ($1, $2, $3, $4, $5, $6, $7, false)
            RETURNING id_ips
            `,
            [
              nitIps || null,
              hospital || null,
              nitIps || null,
              null,
              null,
              departamento || null,
              municipio || null,
            ]
          );
          ipsInfo = {
            id_ips: insertRes.rows[0].id_ips,
            nombre_ips: hospital || null,
            tiene_convenio: false,
            departamento: departamento || null,
            municipio: municipio || null,
          };
        }

        if (ipsInfo) {
          ipsCache.set(ipsKey, ipsInfo);
        }
      }

      if (!ipsInfo) {
        resumen.omitidas += 1;
        resumen.sin_ips += 1;
        if (resumen.errores.length < 50) {
          resumen.errores.push({ fila: i, error: 'IPS no encontrada' });
        }
        continue;
      }

      if (!ipsInfo.tiene_convenio) {
        resumen.ips_sin_convenio += 1;
      }

      const servicioExcel = getValue(row, 'SERVICIO');
      const servicioInfo = parseServicio(servicioExcel);

      const dupRes = await client.query(
        `
        SELECT 1
        FROM autorizacion
        WHERE interno = $1
          AND id_ips = $2
          AND cup_codigo = $3
          AND tipo_autorizacion = $4
          AND COALESCE(tipo_servicio, '') = $5
        LIMIT 1
        `,
        [
          interno,
          ipsInfo.id_ips,
          cupCodigo,
          servicioInfo.tipo_autorizacion,
          servicioInfo.tipo_servicio || '',
        ]
      );

      if (dupRes.rows.length > 0) {
        resumen.omitidas += 1;
        resumen.duplicados += 1;
        continue;
      }

      const observaciones = [];
      const obs1 = getValue(row, 'OBSERVACIONES');
      const obs2 = getValue(row, 'OBSERVACIONES1');
      const solicitante = getValue(row, 'QUIENAUTORIZA');

      if (obs1) observaciones.push(obs1);
      if (obs2) observaciones.push(obs2);
      if (solicitante) observaciones.push(`Solicitante: ${solicitante}`);
      if (!ipsInfo.tiene_convenio) {
        observaciones.push(`IPS sin convenio${hospital ? `: ${hospital}` : ''}`);
      }
      if (!cupCubierto) {
        observaciones.push(`CUPS no cubierto: ${cupCodigo}`);
      }
      if (usuario?.nombre_completo) {
        observaciones.push(`Cargado por: ${usuario.nombre_completo}`);
      }

      const observacionFinal = observaciones.filter(Boolean).join(' | ') || null;

      await client.query('SAVEPOINT row_sp');
      try {
        const insertRes = await client.query(
          `
          INSERT INTO autorizacion
            (interno, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio, estado_autorizacion)
          VALUES ($1, $2, $3, COALESCE($4::date, current_date), $5, $6, $7, $8, 'pendiente')
          RETURNING numero_autorizacion, fecha_autorizacion, version;
          `,
          [
            interno,
            ipsInfo.id_ips,
            autorizanteDefault,
            null,
            observacionFinal,
            cupCodigo,
            servicioInfo.tipo_autorizacion,
            servicioInfo.tipo_servicio,
          ]
        );

        const nuevaAut = insertRes.rows[0];

        await client.query(
          `
          INSERT INTO autorizacion_version
            (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio)
          VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
          `,
          [
            nuevaAut.numero_autorizacion,
            nuevaAut.version || 1,
            ipsInfo.id_ips,
            autorizanteDefault,
            nuevaAut.fecha_autorizacion || null,
            observacionFinal,
            cupCodigo,
            servicioInfo.tipo_autorizacion,
            servicioInfo.tipo_servicio,
          ]
        );

        await client.query('RELEASE SAVEPOINT row_sp');
        resumen.creadas += 1;
      } catch (error) {
        await client.query('ROLLBACK TO SAVEPOINT row_sp');
        resumen.omitidas += 1;
        if (resumen.errores.length < 50) {
          resumen.errores.push({
            fila: i,
            error: error.message || 'Error guardando autorizacion',
          });
        }
      }
    }

    await client.query('COMMIT');
  } catch (error) {
    await client.query('ROLLBACK');
    throw error;
  } finally {
    client.release();
  }

  return {
    ok: true,
    mensaje: 'Carga masiva finalizada',
    ...resumen,
  };
}
// Función auxiliar para generar PDFs
async function generarPdfAutorizacionYObtenerPath(
  numero_autorizacion,
  { tmpDir, fileTag, version } = {}
) {
  const versionNum = Number(version);
  const usarVersion = Number.isInteger(versionNum) && versionNum > 0;

  const sqlActual = `
      SELECT 
        a.numero_autorizacion,
        a.fecha_autorizacion,
        a.observacion,
        a.cup_codigo,
        a.tipo_autorizacion,
        a.tipo_servicio,
        a.version,
        p.*,
        ips.nombre_ips,
        ips.nit,
        ips.direccion,
        ips.municipio,
        ips.departamento,
        aut.nombre   AS nombre_autorizante,
        aut.cargo    AS cargo_autorizante,
        aut.telefono AS tel_autorizante,
        e.nombre_establecimiento,
        e.epc_departamento,
        cc.descripcion AS cup_descripcion,
        cc.nivel AS cup_nivel,
        cc.especialidad AS cup_especialidad
      FROM autorizacion a
      JOIN paciente p
        ON a.interno = p.interno
      JOIN ips
        ON a.id_ips = ips.id_ips
      JOIN autorizante aut
        ON a.numero_documento_autorizante = aut.numero_documento
      JOIN ingreso i
        ON a.interno = i.interno
      JOIN establecimiento e
        ON i.codigo_establecimiento = e.codigo_establecimiento
      LEFT JOIN cups_cubiertos cc
        ON a.cup_codigo = cc.codigo
      WHERE a.numero_autorizacion = $1
      LIMIT 1;
    `;

  let sql;
  let params;

  if (usarVersion) {
    sql = `
      SELECT 
        a.numero_autorizacion,
        av.fecha_autorizacion,
        av.observacion,
        av.cup_codigo,
        av.tipo_autorizacion,
        av.tipo_servicio,
        av.version AS version,
        p.*,
        ips.nombre_ips,
        ips.nit,
        ips.direccion,
        ips.municipio,
        ips.departamento,
        aut.nombre   AS nombre_autorizante,
        aut.cargo    AS cargo_autorizante,
        aut.telefono AS tel_autorizante,
        e.nombre_establecimiento,
        e.epc_departamento,
        cc.descripcion AS cup_descripcion,
        cc.nivel AS cup_nivel,
        cc.especialidad AS cup_especialidad
      FROM autorizacion a
      JOIN autorizacion_version av
        ON a.numero_autorizacion = av.numero_autorizacion
      JOIN paciente p
        ON a.interno = p.interno
      JOIN ips
        ON av.id_ips = ips.id_ips
      JOIN autorizante aut
        ON av.numero_documento_autorizante = aut.numero_documento
      JOIN ingreso i
        ON a.interno = i.interno
      JOIN establecimiento e
        ON i.codigo_establecimiento = e.codigo_establecimiento
      LEFT JOIN cups_cubiertos cc
        ON av.cup_codigo = cc.codigo
      WHERE a.numero_autorizacion = $1
        AND av.version = $2
      LIMIT 1;
    `;
    params = [numero_autorizacion, versionNum];
  } else {
    sql = sqlActual;
    params = [numero_autorizacion];
  }

  let { rows } = await pool.query(sql, params);

  if (rows.length === 0 && usarVersion) {
    const currentVersionRes = await pool.query(
      'SELECT version FROM autorizacion WHERE numero_autorizacion = $1',
      [numero_autorizacion]
    );
    const currentVersion = currentVersionRes.rows[0]?.version;

    if (Number(currentVersion) === versionNum) {
      const fallbackRes = await pool.query(sqlActual, [numero_autorizacion]);
      rows = fallbackRes.rows;
    }
  }

  if (rows.length === 0) {
    const err = new Error('Autorizacion no encontrada');
    err.code = 'NOT_FOUND';
    throw err;
  }

  const a = rows[0];

  // 1) Crear el Excel con la plantilla segun tipo
  const tipoAutorizacion = (a.tipo_autorizacion || 'consultas_externas').toLowerCase();
  const libro = tipoAutorizacion === 'brigadas_ambulancias_hospitalarios'
    ? await crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(a)
    : await crearLibroAutorizacion(a);

  // 2) Guardar XLSX y PDF en temporales
  const baseTmpDir = tmpDir || path.join(os.tmpdir(), 'salud_ut_autorizaciones');
  await fsPromises.mkdir(baseTmpDir, { recursive: true });
  const tag = fileTag ? `_${fileTag}` : '';

  const xlsxPath = path.join(baseTmpDir, `autorizacion_${numero_autorizacion}${tag}.xlsx`);
  const pdfPath  = path.join(baseTmpDir, `autorizacion_${numero_autorizacion}${tag}.pdf`);

  await libro.xlsx.writeFile(xlsxPath);

  // 3) Convertir XLSX -> PDF
  await new Promise((resolve, reject) => {
    execFile(
      sofficePath,
      [
        '--headless',
        '--convert-to', 'pdf',
        '--outdir', baseTmpDir,
        xlsxPath,
      ],
      (error, stdout, stderr) => {
        console.log('soffice stdout:', stdout);
        console.log('soffice stderr:', stderr);
        if (error) return reject(error);
        resolve();
      }
    );
  });

  // Borramos el XLSX, dejamos el PDF
  try {
    await fsPromises.unlink(xlsxPath);
  } catch (e) {
    console.warn('No se pudo borrar XLSX temporal:', e.message);
  }

  return pdfPath; // devolvemos ruta del PDF
}

async function generarZipAutorizacionesPorFecha(fechaInicio, fechaFin, jobId) {
  const sqlNumeros = `
    SELECT a.numero_autorizacion
    FROM autorizacion a
    WHERE a.fecha_autorizacion BETWEEN $1 AND $2
    ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC
  `;

  const { rows } = await pool.query(sqlNumeros, [fechaInicio, fechaFin]);
  if (rows.length === 0) {
    const err = new Error('No hay autorizaciones en ese rango de fechas.');
    err.code = 'EMPTY_RANGE';
    throw err;
  }

  const jobTmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
  await fsPromises.mkdir(jobTmpDir, { recursive: true });

  const zipPath = path.join(
    jobTmpDir,
    `autorizaciones_${fechaInicio}_${fechaFin}.zip`
  );

  const output = fs.createWriteStream(zipPath);
  const archive = archiver('zip', { zlib: { level: 9 } });

  const sqlDetalle = `
    SELECT 
      a.numero_autorizacion,
      a.fecha_autorizacion,
      a.observacion,
      a.cup_codigo,
      a.tipo_autorizacion,
      a.tipo_servicio,
      p.*,
      ips.nombre_ips,
      ips.nit,
      ips.direccion,
      ips.municipio,
      ips.departamento,
      aut.nombre   AS nombre_autorizante,
      aut.cargo    AS cargo_autorizante,
      aut.telefono AS tel_autorizante,
      e.nombre_establecimiento,
      e.epc_departamento,
      cc.descripcion AS cup_descripcion,
      cc.nivel AS cup_nivel,
      cc.especialidad AS cup_especialidad
    FROM autorizacion a
    JOIN paciente p  ON a.interno = p.interno
    JOIN ips         ON a.id_ips = ips.id_ips
    JOIN autorizante aut
          ON a.numero_documento_autorizante = aut.numero_documento
    JOIN ingreso i   ON a.interno = i.interno
    JOIN establecimiento e
          ON i.codigo_establecimiento = e.codigo_establecimiento
    LEFT JOIN cups_cubiertos cc
          ON a.cup_codigo = cc.codigo
    WHERE a.numero_autorizacion = $1
    LIMIT 1;
  `;

  archive.pipe(output);

  for (const row of rows) {
    const numero = row.numero_autorizacion;
    const det = await pool.query(sqlDetalle, [numero]);
    if (det.rows.length === 0) continue;

    const datosAut = det.rows[0];
    const tipoAutorizacion = (datosAut.tipo_autorizacion || 'consultas_externas').toLowerCase();
    const libro = tipoAutorizacion === 'brigadas_ambulancias_hospitalarios'
      ? await crearLibroAutorizacionBrigadasAmbulanciasHospitalarios(datosAut)
      : await crearLibroAutorizacion(datosAut);

    const xlsxPath = path.join(jobTmpDir, `aut_${numero}.xlsx`);
    const pdfPath = path.join(jobTmpDir, `aut_${numero}.pdf`);

    await libro.xlsx.writeFile(xlsxPath);

    await new Promise((resolve, reject) => {
      execFile(
        sofficePath,
        [
          '--headless',
          '--convert-to', 'pdf',
          '--outdir', jobTmpDir,
          xlsxPath,
        ],
        (error, stdout, stderr) => {
          console.log('soffice stdout:', stdout);
          console.log('soffice stderr:', stderr);
          if (error) return reject(error);
          resolve();
        }
      );
    });

    archive.file(pdfPath, { name: `autorizacion_${numero}.pdf` });
  }

  await new Promise((resolve, reject) => {
    output.on('close', resolve);
    archive.on('error', reject);
    archive.finalize();
  });

  const files = await fsPromises.readdir(jobTmpDir);
  await Promise.all(
    files
      .filter((file) => file !== path.basename(zipPath))
      .map((file) => safeUnlink(path.join(jobTmpDir, file)))
  );

  return zipPath;
}

// RESTO DE RUTAS DE LA API
app.get('/api/jobs/:id', verificarToken, (req, res) => {
  const { id } = req.params;
  const job = jobs.get(id);
  if (!job) {
    return res.status(404).json({ error: 'Job no encontrado' });
  }

  if (job.ownerId !== req.usuario.id_usuario && req.usuario.nombre_rol !== 'administrador') {
    return res.status(403).json({ error: 'No tienes permisos para ver este job' });
  }

  return res.json(sanitizeJob(job));
});

app.get('/api/jobs/:id/download', verificarToken, (req, res) => {
  const { id } = req.params;
  const job = jobs.get(id);
  if (!job) {
    return res.status(404).json({ error: 'Job no encontrado' });
  }

  if (job.ownerId !== req.usuario.id_usuario && req.usuario.nombre_rol !== 'administrador') {
    return res.status(403).json({ error: 'No tienes permisos para descargar este archivo' });
  }

  if (job.status !== 'completed' || !job.result?.filePath) {
    return res.status(409).json({ error: 'El archivo aún no está listo' });
  }

  const fileName = job.result.fileName || 'archivo';
  const contentType = job.result.contentType || 'application/octet-stream';

  res.setHeader('Content-Type', contentType);
  res.setHeader('Content-Disposition', `attachment; filename="${fileName}"`);

  const stream = fs.createReadStream(job.result.filePath);
  stream.on('error', (error) => {
    console.error('Error enviando archivo del job:', error);
    res.status(500).end();
  });
  stream.pipe(res);
});

const crearJobPdfHandler = async (req, res) => {
  const numero_autorizacion =
    req.body?.numero_autorizacion || req.query?.numero_autorizacion;
  const version = req.body?.version || req.query?.version;

  if (!numero_autorizacion) {
    return res.status(400).json({ error: 'Falta parámetro numero_autorizacion' });
  }

  const jobId = createJobId();
  const job = {
    id: jobId,
    type: 'pdf-autorizacion',
    status: 'queued',
    createdAt: new Date().toISOString(),
    ownerId: req.usuario.id_usuario,
    meta: {
      version: version || null,
    },
  };

  jobs.set(jobId, job);

  enqueueJob(job, async () => {
    const tmpDir = path.join(os.tmpdir(), 'saludut_jobs', jobId);
    const pdfPath = await generarPdfAutorizacionYObtenerPath(numero_autorizacion, {
      tmpDir,
      fileTag: jobId,
      version,
    });

    const versionTag = version ? `_v${version}` : '';
    return {
      ok: true,
      mensaje: 'PDF generado correctamente',
      filePath: pdfPath,
      fileName: `autorizacion_${numero_autorizacion}${versionTag}.pdf`,
      contentType: 'application/pdf',
      downloadUrl: `/api/jobs/${jobId}/download`,
    };
  });

  return res.status(202).json(sanitizeJob(job));
};

const crearJobZipHandler = async (req, res) => {
  const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
  const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;

  if (!fecha_inicio || !fecha_fin) {
    return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
  }

  const jobId = createJobId();
  const job = {
    id: jobId,
    type: 'zip-autorizaciones',
    status: 'queued',
    createdAt: new Date().toISOString(),
    ownerId: req.usuario.id_usuario,
  };

  jobs.set(jobId, job);

  enqueueJob(job, async () => {
    const zipPath = await generarZipAutorizacionesPorFecha(fecha_inicio, fecha_fin, jobId);
    return {
      ok: true,
      mensaje: 'ZIP generado correctamente',
      filePath: zipPath,
      fileName: `autorizaciones_${fecha_inicio}_${fecha_fin}.zip`,
      contentType: 'application/zip',
      downloadUrl: `/api/jobs/${jobId}/download`,
    };
  });

  return res.status(202).json(sanitizeJob(job));
};

app.post('/api/jobs/autorizacion-pdf', verificarToken, puedeDescargarPdfAutorizacion, crearJobPdfHandler);
app.get('/api/jobs/autorizacion-pdf', verificarToken, puedeDescargarPdfAutorizacion, crearJobPdfHandler);

app.post('/api/jobs/autorizaciones-zip', verificarToken, esAdministrador, crearJobZipHandler);
app.get('/api/jobs/autorizaciones-zip', verificarToken, esAdministrador, crearJobZipHandler);

// CUPS cubiertos
app.get('/api/cups-cubiertos', verificarToken, async (req, res) => {
  const qRaw = req.query.q || '';
  const q = String(qRaw).trim();
  const qNormalized = normalizeSearch(q);

  try {
    let sql = `
      SELECT codigo, descripcion, nivel, especialidad
      FROM cups_cubiertos
      WHERE activo = true
    `;
    const params = [];

    if (q) {
      params.push(`%${q}%`, `%${qNormalized}%`);
      sql += ` AND (codigo ILIKE $1 OR descripcion ILIKE $1 OR descripcion_busqueda LIKE $2)`;
    }

    sql += ' ORDER BY codigo ASC LIMIT 50;';

    const { rows } = await pool.query(sql, params);
    res.json(rows);
  } catch (error) {
    console.error('Error en /api/cups-cubiertos:', error.message);
    res.status(500).json({ error: 'Error consultando CUPS cubiertos' });
  }
});

// CUPS completos con indicador de cobertura
app.get('/api/cups', verificarToken, async (req, res) => {
  const qRaw = req.query.q || '';
  const q = String(qRaw).trim();
  const qNormalized = normalizeSearch(q);
  const limit = Math.min(Number(req.query.limit) || 100, 500);

  try {
    let sql = `
      SELECT
        r.codigo,
        COALESCE(c.descripcion, r.descripcion) AS descripcion,
        c.nivel,
        c.especialidad,
        (c.codigo IS NOT NULL AND c.activo = true) AS cubierto
      FROM cups_referencia r
      LEFT JOIN cups_cubiertos c ON c.codigo = r.codigo
      WHERE 1 = 1
    `;
    const params = [];

    if (q) {
      params.push(`%${q}%`, `%${qNormalized}%`);
      sql += ` AND (r.codigo ILIKE $1 OR r.descripcion ILIKE $1 OR c.descripcion ILIKE $1 OR c.descripcion_busqueda LIKE $2)`;
    }

    params.push(limit);
    sql += ` ORDER BY r.codigo ASC LIMIT $${params.length};`;

    const { rows } = await pool.query(sql, params);
    res.json(rows);
  } catch (error) {
    console.error('Error en /api/cups:', error.message);
    res.status(500).json({ error: 'Error consultando CUPS' });
  }
});

app.get('/api/pacientes', verificarToken, async (req, res) => {
  const { numero_documento, interno, nombre } = req.query;

  if (!numero_documento && !interno && !nombre) {
    return res.status(400).json({
      error: 'Debe enviar al menos uno: numero_documento, interno o nombre',
    });
  }

  let sql = `
    SELECT 
      p.interno,
      p.tipo_documento,
      p.numero_documento,
      p.primer_apellido,
      p.segundo_apellido,
      p.primer_nombre,
      p.segundo_nombre,
      p.fecha_nacimiento,
      p.edad,
      p.sexo,
      i.codigo_establecimiento,
      e.nombre_establecimiento,
      e.epc_departamento,
      e.epc_ciudad,
      i.estado,
      i.fecha_ingreso,
      i.tiempo_reclusion
    FROM paciente p
    LEFT JOIN ingreso i ON p.interno = i.interno
    LEFT JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento
    WHERE 1 = 1
  `;

  const params = [];

  if (numero_documento) {
    params.push(`%${numero_documento}%`);
    sql += ` AND p.numero_documento ILIKE $${params.length}`;
  }

  if (interno) {
    params.push(`%${interno}%`);
    sql += ` AND p.interno ILIKE $${params.length}`;
  }

  if (nombre) {
    params.push(`%${nombre}%`);
    sql += `
      AND (
        (p.primer_nombre || ' ' || COALESCE(p.segundo_nombre, '') || ' ' ||
         p.primer_apellido || ' ' || COALESCE(p.segundo_apellido, '')
        ) ILIKE $${params.length}
      )
    `;
  }


  sql += ' ORDER BY p.interno LIMIT 50;';

  try {
    const { rows } = await pool.query(sql, params);
    res.json(rows);
  } catch (err) {
    console.error('Error en consulta /api/pacientes:', err.message);
    res.status(500).json({ error: 'Error consultando la base de datos' });
  }
});

/**
 * GET /api/ips-por-interno?interno=1007362
 * Busca el departamento del establecimiento del interno
 * y devuelve las IPS de ese departamento (o Bogotá).
 */
app.get('/api/ips-por-interno', verificarToken,  async (req, res) => {
  const { interno, ver_todas } = req.query;

  if (!interno) {
    return res.status(400).json({ error: 'Falta parámetro interno' });
  }

  try {
    // 1) Obtener el departamento y municipio del establecimiento donde está el interno
    const qDepto = `
      SELECT e.epc_departamento, e.epc_ciudad
      FROM ingreso i
      JOIN establecimiento e
        ON i.codigo_establecimiento = e.codigo_establecimiento
      WHERE i.interno = $1
      LIMIT 1;
    `;
    const deptoRes = await pool.query(qDepto, [interno]);

    if (deptoRes.rows.length === 0) {
      return res.json([]); // interno sin ingreso o sin establecimiento
    }

    const departamento = (deptoRes.rows[0].epc_departamento || '').trim();
    const municipioRaw = (deptoRes.rows[0].epc_ciudad || '').trim();
    const municipioUpper = municipioRaw.toUpperCase();

    // Detectar Bogotá aunque venga como "BOGOTA D.C.", "BOGOTA DISTRITO CAPITAL", etc.
    const esBogota =
      municipioUpper.includes('BOGOTA') ||
      departamento.toUpperCase().includes('BOGOTA');

    let qIps;
    let params;
    const verTodas = String(ver_todas || '').toLowerCase();

    if (verTodas === '1' || verTodas === 'true' || verTodas === 'si') {
      qIps = `
        SELECT id_ips, nombre_ips, direccion, telefono, departamento, municipio, tiene_convenio
        FROM ips
        ORDER BY nombre_ips;
      `;
      params = [];
    } else if (esBogota) {
      // Para Bogotá buscamos por "BOGOTA" en municipio o departamento
      qIps = `
        SELECT id_ips, nombre_ips, direccion, telefono, departamento, municipio, tiene_convenio
        FROM ips
        WHERE tiene_convenio = true
          AND (municipio ILIKE $1
            OR departamento ILIKE $1)
        ORDER BY nombre_ips;
      `;
      params = ['%BOGOTA%'];
    } else {
      // Resto del país: por departamento
      qIps = `
        SELECT id_ips, nombre_ips, direccion, telefono, departamento, municipio, tiene_convenio
        FROM ips
        WHERE tiene_convenio = true
          AND departamento ILIKE $1
        ORDER BY nombre_ips;
      `;
      params = [`%${departamento}%`];
    }

    // (Opcional) log para depurar si algo raro pasa
    console.log('[IPS-por-interno] interno', interno,
                'depto:', departamento,
                'muni:', municipioRaw,
                'esBogota?', esBogota);

    const { rows } = await pool.query(qIps, params);
    res.json(rows);
  } catch (err) {
    console.error('Error en /api/ips-por-interno:', err.message);
    res.status(500).json({ error: 'Error consultando IPS' });
  }
});

/**
 * GET /api/autorizantes
 * Lista las personas que pueden autorizar (activos).
 */
app.get('/api/autorizantes', async (_req, res) => {
  try {
    const sql = `
      SELECT numero_documento, tipo_documento, nombre, telefono, cargo, activo
      FROM autorizante
      WHERE activo = true
      ORDER BY nombre;
    `;
    const { rows } = await pool.query(sql);
    res.json(rows);
  } catch (err) {
    console.error('Error en /api/autorizantes:', err.message);
    res.status(500).json({ error: 'Error consultando autorizantes' });
  }
});

/**
 * GET /api/autorizantes/admin
 * Lista todas las personas autorizantes (activos e inactivos) solo para admin.
 */
app.get('/api/autorizantes/admin', verificarToken, esAdministrador, async (_req, res) => {
  try {
    const sql = `
      SELECT numero_documento, tipo_documento, nombre, telefono, cargo, activo
      FROM autorizante
      ORDER BY nombre;
    `;
    const { rows } = await pool.query(sql);
    res.json(rows);
  } catch (err) {
    console.error('Error en /api/autorizantes/admin:', err.message);
    res.status(500).json({ error: 'Error consultando autorizantes' });
  }
});

/**
 * PATCH /api/autorizantes/:numero_documento/estado
 * Body: { activo: boolean }
 */
app.patch('/api/autorizantes/:numero_documento/estado', verificarToken, esAdministrador, async (req, res) => {
  const { numero_documento } = req.params;
  const { activo } = req.body || {};

  const numero = Number(numero_documento);
  if (!Number.isFinite(numero)) {
    return res.status(400).json({ error: 'numero_documento invalido' });
  }

  if (typeof activo !== 'boolean') {
    return res.status(400).json({ error: 'activo debe ser boolean' });
  }

  try {
    const sql = `
      UPDATE autorizante
      SET activo = $1
      WHERE numero_documento = $2
      RETURNING numero_documento, tipo_documento, nombre, telefono, cargo, activo;
    `;
    const { rows } = await pool.query(sql, [activo, numero]);
    if (rows.length === 0) {
      return res.status(404).json({ error: 'Autorizante no encontrado' });
    }
    return res.json({
      mensaje: 'Estado actualizado correctamente',
      autorizante: rows[0],
    });
  } catch (error) {
    console.error('Error en /api/autorizantes/:numero_documento/estado:', error);
    return res.status(500).json({ error: 'Error actualizando autorizante' });
  }
});

/**
 * PATCH /api/autorizantes/:numero_documento
 * Body: { telefono?: string | null, cargo?: string | null }
 */
app.patch('/api/autorizantes/:numero_documento', verificarToken, esAdministrador, async (req, res) => {
  const { numero_documento } = req.params;
  const { telefono, cargo, nombre } = req.body || {};

  const numero = Number(numero_documento);
  if (!Number.isFinite(numero)) {
    return res.status(400).json({ error: 'numero_documento invalido' });
  }

  if (telefono === undefined && cargo === undefined && nombre === undefined) {
    return res.status(400).json({ error: 'Debes enviar al menos un campo a actualizar' });
  }

  const updates = [];
  const values = [];
  let idx = 1;
  let idRolActualizado = null;

  if (nombre !== undefined) {
    const nombreValue = String(nombre || '').trim();
    if (!nombreValue) {
      return res.status(400).json({ error: 'nombre no puede estar vacio' });
    }
    updates.push(`nombre = $${idx++}`);
    values.push(nombreValue);
  }

  if (telefono !== undefined) {
    const telValue = String(telefono || '').trim();
    updates.push(`telefono = $${idx++}`);
    values.push(telValue.length ? telValue : null);
  }

  if (cargo !== undefined) {
    const cargoValue = String(cargo || '').trim();
    updates.push(`cargo = $${idx++}`);
    values.push(cargoValue.length ? cargoValue : null);
  }

  values.push(numero);

  try {
    const sql = `
      UPDATE autorizante
      SET ${updates.join(', ')}
      WHERE numero_documento = $${idx}
      RETURNING numero_documento, tipo_documento, nombre, telefono, cargo, activo;
    `;
    const { rows } = await pool.query(sql, values);
    if (rows.length === 0) {
      return res.status(404).json({ error: 'Autorizante no encontrado' });
    }
    return res.json({
      mensaje: 'Autorizante actualizado correctamente',
      autorizante: rows[0],
    });
  } catch (error) {
    console.error('Error en /api/autorizantes/:numero_documento:', error);
    return res.status(500).json({ error: 'Error actualizando autorizante' });
  }
});

/**
 * POST /api/autorizantes
 * Body JSON:
 * {
 *   "numero_documento": 12345678,
 *   "tipo_documento": "CC",
 *   "nombre": "Nombre Apellido",
 *   "telefono": "3000000000",
 *   "cargo": "Cargo",
 *   "activo": true
 * }
 */
app.post('/api/autorizantes', verificarToken, esAdministrador, async (req, res) => {
  const {
    numero_documento,
    tipo_documento,
    nombre,
    telefono,
    cargo,
    activo,
  } = req.body || {};

  if (!numero_documento || !tipo_documento || !nombre) {
    return res.status(400).json({
      error: 'numero_documento, tipo_documento y nombre son obligatorios',
    });
  }

  const tipo = String(tipo_documento).trim().toUpperCase();
  if (!['CC', 'DE', 'RC'].includes(tipo)) {
    return res.status(400).json({ error: 'tipo_documento invalido' });
  }

  const numero = Number(numero_documento);
  if (!Number.isFinite(numero)) {
    return res.status(400).json({ error: 'numero_documento debe ser numerico' });
  }

  const activoValue = activo === undefined ? true : !!activo;

  try {
    const sql = `
      INSERT INTO autorizante (numero_documento, tipo_documento, nombre, telefono, cargo, activo)
      VALUES ($1, $2, $3, $4, $5, $6)
      ON CONFLICT (numero_documento) DO UPDATE SET
        tipo_documento = EXCLUDED.tipo_documento,
        nombre = EXCLUDED.nombre,
        telefono = EXCLUDED.telefono,
        cargo = EXCLUDED.cargo,
        activo = EXCLUDED.activo
      RETURNING numero_documento, tipo_documento, nombre, telefono, cargo, activo;
    `;
    const { rows } = await pool.query(sql, [
      numero,
      tipo,
      String(nombre).trim(),
      telefono ? String(telefono).trim() : null,
      cargo ? String(cargo).trim() : null,
      activoValue,
    ]);

    return res.status(201).json({
      mensaje: 'Autorizante creado correctamente',
      autorizante: rows[0],
    });
  } catch (error) {
    console.error('Error en /api/autorizantes (POST):', error);
    return res.status(500).json({ error: 'Error creando autorizante' });
  }
});

/**
 * POST /api/autorizaciones
 * Body JSON:
 * {
 *   "interno": "1007362",
 *   "id_ips": 15,
 *   "numero_documento_autorizante": 12345678,
 *   "observacion": "Consulta oftalmología",
 *   "fecha_autorizacion": "2025-11-25"  // opcional
 * }
 */
app.post('/api/autorizaciones', verificarToken, puedeGenerarAutorizaciones, async (req, res) => {
  const {
    interno,
    id_ips,
    numero_documento_autorizante,
    observacion,
    fecha_autorizacion,
    cup_codigo,
    tipo_autorizacion,
    tipo_servicio,
  } = req.body;

  if (!interno || !id_ips || !numero_documento_autorizante) {
    return res.status(400).json({
      error: 'interno, id_ips y numero_documento_autorizante son obligatorios',
    });
  }

  const cupCodigo = String(cup_codigo || '').trim();
  if (!cupCodigo) {
    return res.status(400).json({ error: 'cup_codigo es obligatorio' });
  }

  const tipoAutorizacion = String(tipo_autorizacion || 'consultas_externas')
    .trim()
    .toLowerCase();
  const tiposPermitidos = [
    'consultas_externas',
    'brigadas_ambulancias_hospitalarios',
  ];
  if (!tiposPermitidos.includes(tipoAutorizacion)) {
    return res.status(400).json({ error: 'tipo_autorizacion invalido' });
  }

  let tipoServicio = null;
  if (tipoAutorizacion === 'brigadas_ambulancias_hospitalarios') {
    const servicio = String(tipo_servicio || '').trim().toLowerCase();
    const serviciosPermitidos = ['brigadas', 'ambulancias', 'hospitalarios'];
    if (!serviciosPermitidos.includes(servicio)) {
      return res.status(400).json({
        error: 'tipo_servicio es obligatorio para este tipo de autorizacion',
      });
    }
    tipoServicio = servicio;
  }

  // ... aquí va tu lógica de permisos por rol / sede ...

  const client = await pool.connect();

  try {
    await client.query('BEGIN');

    const sql = `
      INSERT INTO autorizacion
      (
        interno,
        id_ips,
        numero_documento_autorizante,
        fecha_autorizacion,
        observacion,
        cup_codigo,
        tipo_autorizacion,
        tipo_servicio
      )
      VALUES ($1, $2, $3, COALESCE($4::date, current_date), $5, $6, $7, $8)
      RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, tipo_autorizacion, tipo_servicio, version, estado_autorizacion;
    `;

    const params = [
      interno,
      id_ips,
      numero_documento_autorizante,
      fecha_autorizacion || null,
      observacion || null,
      cupCodigo,
      tipoAutorizacion,
      tipoServicio,
    ];

    const { rows } = await client.query(sql, params);
    const nuevaAutorizacion = rows[0];

    await client.query(
      `
      INSERT INTO autorizacion_version
        (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
      `,
      [
        nuevaAutorizacion.numero_autorizacion,
        nuevaAutorizacion.version || 1,
        id_ips,
        numero_documento_autorizante,
        nuevaAutorizacion.fecha_autorizacion || null,
        observacion || null,
        cupCodigo,
        tipoAutorizacion,
        tipoServicio,
      ]
    );

    await client.query('COMMIT');

    const cupInfoRes = await pool.query(
      'SELECT descripcion, nivel, especialidad FROM cups_cubiertos WHERE codigo = $1 AND activo = true',
      [cupCodigo]
    );
    const cupInfo = cupInfoRes.rows[0] || null;

    res.status(201).json({
      ...nuevaAutorizacion,
      cup_descripcion: cupInfo?.descripcion || null,
      cup_nivel: cupInfo?.nivel || null,
      cup_especialidad: cupInfo?.especialidad || null,
    });
  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Error en /api/autorizaciones:', err.message);
    res.status(500).json({ error: 'Error guardando autorización' });
  } finally {
    client.release();
  }
});

/**
 * GET /api/autorizaciones/:numero_autorizacion/versiones
 * Lista versiones disponibles de una autorizacion.
 */
app.get(
  '/api/autorizaciones/:numero_autorizacion/versiones',
  verificarToken,
  puedeGenerarAutorizaciones,
  async (req, res) => {
    const { numero_autorizacion } = req.params;

    try {
      const actualRes = await pool.query(
        'SELECT version, fecha_autorizacion FROM autorizacion WHERE numero_autorizacion = $1',
        [numero_autorizacion]
      );

      if (actualRes.rows.length === 0) {
        return res.status(404).json({ error: 'Autorizacion no encontrada' });
      }

      const versionActual = actualRes.rows[0]?.version || 1;
      const versionesRes = await pool.query(
        `
        SELECT version, fecha_version, fecha_autorizacion
        FROM autorizacion_version
        WHERE numero_autorizacion = $1
        ORDER BY version DESC;
        `,
        [numero_autorizacion]
      );

      if (versionesRes.rows.length === 0) {
        return res.json({
          version_actual: versionActual,
          versiones: [
            {
              version: versionActual,
              fecha_version: actualRes.rows[0]?.fecha_autorizacion || null,
              fecha_autorizacion: actualRes.rows[0]?.fecha_autorizacion || null,
            },
          ],
        });
      }

      return res.json({
        version_actual: versionActual,
        versiones: versionesRes.rows,
      });
    } catch (err) {
      console.error('Error en /api/autorizaciones/:numero/versiones:', err.message);
      res.status(500).json({ error: 'Error consultando versiones' });
    }
  }
);

/**
 * PUT /api/autorizaciones/:numero_autorizacion
 * Actualiza una autorizacion y crea una nueva version.
 */
app.put('/api/autorizaciones/:numero_autorizacion', verificarToken, puedeGenerarAutorizaciones, async (req, res) => {
  const { numero_autorizacion } = req.params;
  const {
    id_ips,
    numero_documento_autorizante,
    observacion,
    fecha_autorizacion,
    cup_codigo,
    tipo_autorizacion,
    tipo_servicio,
  } = req.body;

  if (!id_ips || !numero_documento_autorizante) {
    return res.status(400).json({
      error: 'id_ips y numero_documento_autorizante son obligatorios',
    });
  }

  const cupCodigo = String(cup_codigo || '').trim();
  if (!cupCodigo) {
    return res.status(400).json({ error: 'cup_codigo es obligatorio' });
  }

  const tipoAutorizacion = String(tipo_autorizacion || 'consultas_externas')
    .trim()
    .toLowerCase();
  const tiposPermitidos = [
    'consultas_externas',
    'brigadas_ambulancias_hospitalarios',
  ];
  if (!tiposPermitidos.includes(tipoAutorizacion)) {
    return res.status(400).json({ error: 'tipo_autorizacion invalido' });
  }

  let tipoServicio = null;
  if (tipoAutorizacion === 'brigadas_ambulancias_hospitalarios') {
    const servicio = String(tipo_servicio || '').trim().toLowerCase();
    const serviciosPermitidos = ['brigadas', 'ambulancias', 'hospitalarios'];
    if (!serviciosPermitidos.includes(servicio)) {
      return res.status(400).json({
        error: 'tipo_servicio es obligatorio para este tipo de autorizacion',
      });
    }
    tipoServicio = servicio;
  }

  const client = await pool.connect();

  try {
    await client.query('BEGIN');

    const actualRes = await client.query(
      'SELECT * FROM autorizacion WHERE numero_autorizacion = $1 FOR UPDATE',
      [numero_autorizacion]
    );

    if (actualRes.rows.length === 0) {
      await client.query('ROLLBACK');
      return res.status(404).json({ error: 'Autorizacion no encontrada' });
    }

    const actual = actualRes.rows[0];
    const versionActual = Number(actual.version) || 1;

    const existeVersion = await client.query(
      'SELECT 1 FROM autorizacion_version WHERE numero_autorizacion = $1 AND version = $2',
      [numero_autorizacion, versionActual]
    );

    if (existeVersion.rows.length === 0) {
      await client.query(
        `
        INSERT INTO autorizacion_version
          (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio)
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
        `,
        [
          numero_autorizacion,
          versionActual,
          actual.id_ips,
          actual.numero_documento_autorizante,
          actual.fecha_autorizacion || null,
          actual.observacion || null,
          actual.cup_codigo || null,
          actual.tipo_autorizacion || null,
          actual.tipo_servicio || null,
        ]
      );
    }

    const nuevaVersion = versionActual + 1;
    const fechaAut = fecha_autorizacion || actual.fecha_autorizacion || null;

    const updateRes = await client.query(
      `
      UPDATE autorizacion
      SET id_ips = $1,
          numero_documento_autorizante = $2,
          fecha_autorizacion = COALESCE($3::date, fecha_autorizacion),
          observacion = $4,
          cup_codigo = $5,
          tipo_autorizacion = $6,
          tipo_servicio = $7,
          version = $8
      WHERE numero_autorizacion = $9
      RETURNING numero_autorizacion, fecha_autorizacion, cup_codigo, tipo_autorizacion, tipo_servicio, version, estado_autorizacion;
      `,
      [
        id_ips,
        numero_documento_autorizante,
        fechaAut,
        observacion || null,
        cupCodigo,
        tipoAutorizacion,
        tipoServicio,
        nuevaVersion,
        numero_autorizacion,
      ]
    );

    const actualizado = updateRes.rows[0];

    await client.query(
      `
      INSERT INTO autorizacion_version
        (numero_autorizacion, version, id_ips, numero_documento_autorizante, fecha_autorizacion, observacion, cup_codigo, tipo_autorizacion, tipo_servicio)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
      `,
      [
        numero_autorizacion,
        nuevaVersion,
        id_ips,
        numero_documento_autorizante,
        actualizado.fecha_autorizacion || null,
        observacion || null,
        cupCodigo,
        tipoAutorizacion,
        tipoServicio,
      ]
    );

    await client.query('COMMIT');

    const cupInfoRes = await pool.query(
      'SELECT descripcion, nivel, especialidad FROM cups_cubiertos WHERE codigo = $1 AND activo = true',
      [cupCodigo]
    );
    const cupInfo = cupInfoRes.rows[0] || null;

    res.json({
      ...actualizado,
      cup_descripcion: cupInfo?.descripcion || null,
      cup_nivel: cupInfo?.nivel || null,
      cup_especialidad: cupInfo?.especialidad || null,
    });
  } catch (err) {
    await client.query('ROLLBACK');
    console.error('Error en PUT /api/autorizaciones:', err.message);
    res.status(500).json({ error: 'Error actualizando autorizacion' });
  } finally {
    client.release();
  }
});

/**
 * PATCH /api/autorizaciones/:numero_autorizacion/estado
 * Body: { estado_autorizacion: 'pendiente' | 'autorizado' | 'no_autorizado' }
 * Solo administrador
 */
app.patch(
  '/api/autorizaciones/:numero_autorizacion/estado',
  verificarToken,
  esAdministrador,
  async (req, res) => {
    const { numero_autorizacion } = req.params;
    const estado = String(req.body?.estado_autorizacion || '')
      .trim()
      .toLowerCase();

    const estadosPermitidos = ['pendiente', 'autorizado', 'no_autorizado'];
    if (!estadosPermitidos.includes(estado)) {
      return res.status(400).json({ error: 'estado_autorizacion invalido' });
    }

    try {
      const { rows } = await pool.query(
        `
        UPDATE autorizacion
        SET estado_autorizacion = $1
        WHERE numero_autorizacion = $2
        RETURNING numero_autorizacion, estado_autorizacion;
        `,
        [estado, numero_autorizacion]
      );

      if (rows.length === 0) {
        return res.status(404).json({ error: 'Autorizacion no encontrada' });
      }

      return res.json({
        mensaje: 'Estado de autorizacion actualizado',
        autorizacion: rows[0],
      });
    } catch (error) {
      console.error('Error actualizando estado de autorizacion:', error.message);
      return res.status(500).json({ error: 'Error actualizando autorizacion' });
    }
  }
);

/**
 * GET /api/autorizaciones?interno=1007362
 * Devuelve todas las autorizaciones del interno.
 */
app.get('/api/autorizaciones', verificarToken, async (req, res) => {
  const { interno } = req.query;

  if (!interno) {
    return res.status(400).json({ error: 'Falta parámetro interno' });
  }

  try {
    const esAdmin = req.usuario?.nombre_rol === 'administrador';
    const sql = `
      SELECT 
        a.numero_autorizacion,
        a.fecha_autorizacion,
        a.observacion,
        a.cup_codigo,
        a.tipo_autorizacion,
        a.tipo_servicio,
        a.version,
        COALESCE(a.estado_autorizacion, 'pendiente') AS estado_autorizacion,
        a.id_ips,
        a.numero_documento_autorizante,
        ips.nombre_ips,
        COALESCE(ips.municipio, e.epc_ciudad) AS municipio,
        COALESCE(ips.departamento, e.epc_departamento) AS departamento,
        COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio,
        aut.nombre AS nombre_autorizante,
        cc.descripcion AS cup_descripcion,
        cc.nivel AS cup_nivel,
        cc.especialidad AS cup_especialidad
      FROM autorizacion a
      JOIN ips
        ON a.id_ips = ips.id_ips
      LEFT JOIN ingreso i
        ON a.interno = i.interno
      LEFT JOIN establecimiento e
        ON i.codigo_establecimiento = e.codigo_establecimiento
      JOIN autorizante aut
        ON a.numero_documento_autorizante = aut.numero_documento
      LEFT JOIN cups_cubiertos cc
        ON a.cup_codigo = cc.codigo
      WHERE a.interno = $1
        ${esAdmin ? '' : "AND COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')"}
      ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC;
    `;

    const { rows } = await pool.query(sql, [interno]);
    res.json(rows);
  } catch (err) {
    console.error('Error en /api/autorizaciones:', err.message);
    res.status(500).json({ error: 'Error consultando autorizaciones' });
  }
});

// Endpoint para generar el Excel de autorizaciones
app.get('/api/generar-excel-autorizaciones', async (req, res) => {
  try {
    // Crear el workbook y la hoja de trabajo
    const workbook = new ExcelJS.Workbook();
    const worksheet = workbook.addWorksheet('Autorizaciones');

    // Agregar encabezados
    worksheet.columns = [
      { header: 'Numero de Autorizacion', key: 'numero_autorizacion', width: 20 },
      { header: 'Version', key: 'version', width: 10 },
      { header: 'Interno', key: 'interno', width: 15 },
      { header: 'Nombre del paciente', key: 'nombre_paciente', width: 30 },
      { header: 'CUPS', key: 'cup_codigo', width: 12 },
      { header: 'Nivel', key: 'cup_nivel', width: 10 },
      { header: 'Tipo autorizacion', key: 'tipo_autorizacion', width: 20 },
      { header: 'Tipo servicio', key: 'tipo_servicio', width: 18 },
      { header: 'IPS', key: 'nombre_ips', width: 30 },
      { header: 'Fecha de autorizacion', key: 'fecha_autorizacion', width: 20 },
      { header: 'Observacion', key: 'observacion', width: 50 },
      { header: 'Autoriza', key: 'autoriza', width: 20 },
    ];

    // Obtener autorizaciones de la base de datos
    const sql = `
      SELECT
        a.numero_autorizacion,
        a.version,
        p.interno,
        p.primer_nombre || ' ' || p.segundo_nombre || ' ' || p.primer_apellido || ' ' || p.segundo_apellido AS nombre_paciente,
        i.nombre_ips,
        a.cup_codigo,
        cc.nivel AS cup_nivel,
        a.tipo_autorizacion,
        a.tipo_servicio,
        a.fecha_autorizacion,
        a.observacion,
        aut.nombre AS autoriza
      FROM autorizacion a
      JOIN paciente p ON a.interno = p.interno
      JOIN ips i ON a.id_ips = i.id_ips
      JOIN autorizante aut ON a.numero_documento_autorizante = aut.numero_documento
      LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo
    `;

    const { rows } = await pool.query(sql);

    // Agregar las filas a la hoja de trabajo
    rows.forEach(row => {
      const tipoServicio = row.tipo_servicio || (
        String(row.tipo_autorizacion || '').toLowerCase() === 'consultas_externas'
          ? 'consulta externa'
          : ''
      );
      worksheet.addRow({
        numero_autorizacion: row.numero_autorizacion,
        version: row.version,
        interno: row.interno,
        nombre_paciente: row.nombre_paciente,
        cup_codigo: row.cup_codigo,
        cup_nivel: row.cup_nivel,
        tipo_autorizacion: row.tipo_autorizacion,
        tipo_servicio: tipoServicio,
        nombre_ips: row.nombre_ips,
        fecha_autorizacion: row.fecha_autorizacion,
        observacion: row.observacion,
        autoriza: row.autoriza,
      });
    });

    // Configurar la respuesta para que sea descargada como archivo Excel
    res.setHeader('Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    res.setHeader('Content-Disposition', 'attachment; filename=autorizaciones.xlsx');

    // Escribir el archivo en la respuesta
    await workbook.xlsx.write(res);
    res.end();
  } catch (err) {
    console.error('Error al generar el Excel:', err.message);
    res.status(500).json({ error: 'Error al generar el archivo Excel' });
  }
});

/**
 * GET /api/generar-pdf-autorizacion?numero_autorizacion=2025-000123
 */
app.get('/api/generar-pdf-autorizacion', verificarToken, puedeDescargarPdfAutorizacion, crearJobPdfHandler);


// ===========================
// ENDPOINTS DE AUTENTICACIÓN
// ===========================

/**
 * POST /api/auth/login
 * Body: { username, password }
 */
app.post('/api/auth/login', async (req, res) => {
  const { username, password } = req.body;

  if (!username || !password) {
    return res.status(400).json({ error: 'Usuario y contraseña son requeridos' });
  }

  try {
    const sql = `
      SELECT u.*, r.nombre_rol
      FROM usuario u
      JOIN rol r ON u.id_rol = r.id_rol
      WHERE u.username = $1 AND u.activo = true
    `;
    
    const { rows } = await pool.query(sql, [username]);
    if (rows.length === 0) {
      return res.status(401).json({ error: 'Credenciales inválidas' });
    }

    const usuario = rows[0];
    const passwordValida = await bcrypt.compare(password, usuario.password_hash);
    
    if (!passwordValida) {
      return res.status(401).json({ error: 'Credenciales inválidas' });
    }

    // Actualizar último login
    await pool.query(
      'UPDATE usuario SET ultimo_login = current_timestamp WHERE id_usuario = $1',
      [usuario.id_usuario]
    );

    // Obtener sedes si es administrativo de sede
    let sedes = [];
    if (usuario.nombre_rol === 'administrativo_sede') {
      const sedesQuery = `
        SELECT us.codigo_establecimiento, e.nombre_establecimiento
        FROM usuario_sede us
        JOIN establecimiento e ON us.codigo_establecimiento = e.codigo_establecimiento
        WHERE us.id_usuario = $1
      `;
      const sedesResult = await pool.query(sedesQuery, [usuario.id_usuario]);
      sedes = sedesResult.rows;
    }

    const token = jwt.sign(
      {
        id_usuario: usuario.id_usuario,
        username: usuario.username,
        nombre_completo: usuario.nombre_completo,
        nombre_rol: usuario.nombre_rol,
        id_rol: usuario.id_rol,
        token_version: usuario.token_version ?? 1
      },
      JWT_SECRET,
      { expiresIn: JWT_EXPIRES_IN }
    );

    res.json({
      token,
      usuario: {
        id_usuario: usuario.id_usuario,
        username: usuario.username,
        nombre_completo: usuario.nombre_completo,
        nombre_rol: usuario.nombre_rol,
        sedes
      }
    });

  } catch (error) {
    console.error('Error en login:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * POST /api/auth/register
 * Body: { username, email, password, nombre_completo, id_rol, sedes? }
 * Solo ADMIN
 */
app.post('/api/auth/register', verificarToken, esAdministrador, async (req, res) => {
  const { username, email, password, nombre_completo, id_rol, sedes } = req.body;

  console.log('Solicitud de registro:', req.body);

  if (!username || !email || !password || !nombre_completo || !id_rol) {
    return res.status(400).json({ error: 'Todos los campos son requeridos' });
  }

  try {
    // Validar contraseña con la función de Postgres
    const sqlValidar = 'SELECT validar_contrasena($1) as valida';
    const { rows } = await pool.query(sqlValidar, [password]);
    
    if (!rows[0].valida) {
      return res.status(400).json({ 
        error: 'La contraseña debe tener al menos 8 caracteres y contener letras y números' 
      });
    }

    // Verificar duplicados
    const checkSql = 'SELECT id_usuario FROM usuario WHERE username = $1 OR email = $2';
    const checkResult = await pool.query(checkSql, [username, email]);
    
    if (checkResult.rows.length > 0) {
      return res.status(400).json({ error: 'El usuario o email ya están registrados' });
    }

    // Encriptar contraseña
    const passwordHash = await bcrypt.hash(password, 10);

    // Insertar usuario
    const insertSql = `
      INSERT INTO usuario (username, email, password_hash, nombre_completo, id_rol)
      VALUES ($1, $2, $3, $4, $5)
      RETURNING id_usuario, username, email, nombre_completo, id_rol, activo, fecha_creacion, ultimo_login
    `;

    const { rows: newUsuario } = await pool.query(insertSql, [
      username, email, passwordHash, nombre_completo, id_rol
    ]);

    const usuarioCreado = newUsuario[0];

    // Si es administrativo_sede, asignar sedes
    if (id_rol == 2) {
      let sedesAsignadas = Array.isArray(sedes) ? sedes : [];
      sedesAsignadas = sedesAsignadas
        .map((s) => String(s || '').trim())
        .filter((s) => s.length > 0);

      if (sedesAsignadas.length === 0) {
        const { rows: sedesRows } = await pool.query(
          'SELECT codigo_establecimiento FROM establecimiento'
        );
        sedesAsignadas = sedesRows.map((row) => row.codigo_establecimiento);
      }

      for (const sede of sedesAsignadas) {
        await pool.query(
          'INSERT INTO usuario_sede (id_usuario, codigo_establecimiento) VALUES ($1, $2) ON CONFLICT (id_usuario, codigo_establecimiento) DO NOTHING',
          [usuarioCreado.id_usuario, sede]
        );
      }
    }

    res.status(201).json({
      mensaje: 'Usuario creado exitosamente',
      usuario: usuarioCreado
    });

  } catch (error) {
    console.error('Error en /api/auth/register:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * GET /api/auth/verify
 */
app.get('/api/auth/verify', verificarToken, async (req, res) => {
  try {
    const sql = `
      SELECT u.id_usuario, u.username, u.nombre_completo, r.nombre_rol
      FROM usuario u
      JOIN rol r ON u.id_rol = r.id_rol
      WHERE u.id_usuario = $1 AND u.activo = true
    `;
    
    const { rows } = await pool.query(sql, [req.usuario.id_usuario]);
    
    if (rows.length === 0) {
      return res.status(401).json({ error: 'Usuario no encontrado' });
    }

    let sedes = [];
    if (rows[0].nombre_rol === 'administrativo_sede') {
      const sedesQuery = `
        SELECT us.codigo_establecimiento, e.nombre_establecimiento
        FROM usuario_sede us
        JOIN establecimiento e ON us.codigo_establecimiento = e.codigo_establecimiento
        WHERE us.id_usuario = $1
      `;
      const sedesResult = await pool.query(sedesQuery, [req.usuario.id_usuario]);
      sedes = sedesResult.rows;
    }

    res.json({
      usuario: {
        ...rows[0],
        sedes
      }
    });

  } catch (error) {
    console.error('Error en verify:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * GET /api/usuarios
 * Solo administrador
 */
app.get('/api/usuarios', verificarToken, esAdministrador, async (req, res) => {
  try {
    const limit = Math.min(Number(req.query.limit) || 200, 1000);
    const offset = Math.max(Number(req.query.offset) || 0, 0);

    const sql = `
      SELECT 
        u.id_usuario,
        u.username,
        u.email,
        u.nombre_completo,
        u.id_rol,
        u.activo,
        u.fecha_creacion,
        u.ultimo_login,
        r.nombre_rol
      FROM usuario u
      JOIN rol r ON u.id_rol = r.id_rol
      ORDER BY u.fecha_creacion ASC
      LIMIT $1 OFFSET $2
    `;
    
    const { rows } = await pool.query(sql, [limit, offset]);
    res.json(rows);

  } catch (error) {
    console.error('Error listando usuarios:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * PATCH /api/usuarios/:id/estado
 * { activo: boolean }
 * Solo administrador
 */
app.patch('/api/usuarios/:id/estado', verificarToken, esAdministrador, async (req, res) => {
  const { id } = req.params;
  const { activo } = req.body;

  if (typeof activo !== 'boolean') {
    return res.status(400).json({ error: 'El campo "activo" debe ser booleano' });
  }

  try {
    const sql = `
      UPDATE usuario
      SET activo = $1,
          token_version = token_version + 1
      WHERE id_usuario = $2
      RETURNING id_usuario, username, email, nombre_completo, activo, token_version
    `;
    const { rows } = await pool.query(sql, [activo, id]);

    if (rows.length === 0) {
      return res.status(404).json({ error: 'Usuario no encontrado' });
    }

    res.json(rows[0]);

  } catch (error) {
    console.error('Error cambiando estado de usuario:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * PATCH /api/usuarios/:id
 * Body: { username?: string, password?: string }
 * Solo administrador
 */
app.patch('/api/usuarios/:id', verificarToken, esAdministrador, async (req, res) => {
  const { id } = req.params;
  const { username, password, email, nombre_completo, id_rol } = req.body || {};

  const idUsuario = Number(id);
  if (!Number.isFinite(idUsuario)) {
    return res.status(400).json({ error: 'id_usuario invalido' });
  }

  if (
    username === undefined &&
    password === undefined &&
    email === undefined &&
    nombre_completo === undefined &&
    id_rol === undefined
  ) {
    return res.status(400).json({ error: 'Debes enviar al menos un campo a actualizar' });
  }

  const updates = [];
  const values = [];
  let idx = 1;

  if (username !== undefined) {
    const usernameValue = String(username || '').trim();
    if (!usernameValue) {
      return res.status(400).json({ error: 'username no puede estar vacio' });
    }

    const checkSql = 'SELECT id_usuario FROM usuario WHERE username = $1 AND id_usuario <> $2';
    const checkResult = await pool.query(checkSql, [usernameValue, idUsuario]);
    if (checkResult.rows.length > 0) {
      return res.status(400).json({ error: 'El usuario ya existe' });
    }

    updates.push(`username = $${idx++}`);
    values.push(usernameValue);
  }

  if (password !== undefined) {
    const passwordValue = String(password || '');
    if (!passwordValue.trim()) {
      return res.status(400).json({ error: 'password no puede estar vacia' });
    }

    const sqlValidar = 'SELECT validar_contrasena($1) as valida';
    const { rows: validacion } = await pool.query(sqlValidar, [passwordValue]);
    if (!validacion[0]?.valida) {
      return res.status(400).json({
        error: 'La contrasena debe tener al menos 8 caracteres y contener letras y numeros'
      });
    }

    const passwordHash = await bcrypt.hash(passwordValue, 10);
    updates.push(`password_hash = $${idx++}`);
    values.push(passwordHash);
  }

  if (email !== undefined) {
    const emailValue = String(email || '').trim();
    if (!emailValue) {
      return res.status(400).json({ error: 'email no puede estar vacio' });
    }

    const checkSql = 'SELECT id_usuario FROM usuario WHERE email = $1 AND id_usuario <> $2';
    const checkResult = await pool.query(checkSql, [emailValue, idUsuario]);
    if (checkResult.rows.length > 0) {
      return res.status(400).json({ error: 'El email ya existe' });
    }

    updates.push(`email = $${idx++}`);
    values.push(emailValue);
  }

  if (nombre_completo !== undefined) {
    const nombreValue = String(nombre_completo || '').trim();
    if (!nombreValue) {
      return res.status(400).json({ error: 'nombre_completo no puede estar vacio' });
    }
    updates.push(`nombre_completo = $${idx++}`);
    values.push(nombreValue);
  }

  if (id_rol !== undefined) {
    const idRol = Number(id_rol);
    if (!Number.isFinite(idRol)) {
      return res.status(400).json({ error: 'id_rol invalido' });
    }

    const rolResult = await pool.query('SELECT id_rol FROM rol WHERE id_rol = $1', [idRol]);
    if (rolResult.rows.length === 0) {
      return res.status(400).json({ error: 'El rol no existe' });
    }

    updates.push(`id_rol = $${idx++}`);
    values.push(idRol);
    idRolActualizado = idRol;
  }

  updates.push('token_version = token_version + 1');
  values.push(idUsuario);

  try {
    const sql = `
      UPDATE usuario
      SET ${updates.join(', ')}
      WHERE id_usuario = $${idx}
      RETURNING id_usuario, username, email, nombre_completo, id_rol, activo;
    `;
    const { rows } = await pool.query(sql, values);

    if (rows.length === 0) {
      return res.status(404).json({ error: 'Usuario no encontrado' });
    }

    if (idRolActualizado === 2) {
      const { rows: existeSede } = await pool.query(
        'SELECT 1 FROM usuario_sede WHERE id_usuario = $1 LIMIT 1',
        [idUsuario]
      );

      if (existeSede.length === 0) {
        const { rows: sedesRows } = await pool.query(
          'SELECT codigo_establecimiento FROM establecimiento'
        );

        for (const sede of sedesRows) {
          await pool.query(
            'INSERT INTO usuario_sede (id_usuario, codigo_establecimiento) VALUES ($1, $2) ON CONFLICT (id_usuario, codigo_establecimiento) DO NOTHING',
            [idUsuario, sede.codigo_establecimiento]
          );
        }
      }
    }

    return res.json({
      mensaje: 'Usuario actualizado correctamente',
      usuario: rows[0]
    });
  } catch (error) {
    console.error('Error actualizando usuario:', error);
    return res.status(500).json({ error: 'Error actualizando usuario' });
  }
});

/**
 * GET /api/roles
 * Lista los roles disponibles (solo administrador)
 */
app.get('/api/roles', verificarToken, esAdministrador, async (req, res) => {
  try {
    const sql = `
      SELECT id_rol, nombre_rol, descripcion
      FROM rol
      ORDER BY id_rol
    `;
    const { rows } = await pool.query(sql);
    res.json(rows);
  } catch (error) {
    console.error('Error listando roles:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * GET /api/autorizaciones-por-fecha
 * Query params: fecha_inicio, fecha_fin
 * Administrador ve todo; otros solo autorizadas
 */
app.get('/api/autorizaciones-por-fecha', verificarToken, async (req, res) => {
  const { fecha_inicio, fecha_fin } = req.query;

  if (!fecha_inicio || !fecha_fin) {
    return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
  }

  try {
    const esAdmin = req.usuario?.nombre_rol === 'administrador';
    const limit = Math.min(Number(req.query.limit) || 500, 2000);
    const offset = Math.max(Number(req.query.offset) || 0, 0);

    const sql = `
      SELECT 
        a.numero_autorizacion,
        a.fecha_autorizacion,
        a.observacion,
        a.cup_codigo,
        a.tipo_autorizacion,
        a.tipo_servicio,
        a.version,
        COALESCE(a.estado_autorizacion, 'pendiente') AS estado_autorizacion,
        p.interno,
          p.primer_nombre || ' ' || COALESCE(p.segundo_nombre, '') || ' ' || 
          p.primer_apellido || ' ' || COALESCE(p.segundo_apellido, '') AS nombre_paciente,
          ips.nombre_ips,
          COALESCE(ips.municipio, e.epc_ciudad) AS municipio,
          COALESCE(ips.departamento, e.epc_departamento) AS departamento,
          COALESCE(ips.tiene_convenio, false) AS ips_tiene_convenio,
            aut.nombre AS nombre_autorizante,
            e.nombre_establecimiento,
          CASE WHEN cc.codigo IS NULL THEN false ELSE true END AS cup_cubierto,
          cc.descripcion AS cup_descripcion,
          cc.nivel AS cup_nivel,
          cc.especialidad AS cup_especialidad
      FROM autorizacion a
      JOIN paciente p ON a.interno = p.interno
      JOIN ips ON a.id_ips = ips.id_ips
      JOIN autorizante aut ON a.numero_documento_autorizante = aut.numero_documento
      JOIN ingreso i ON a.interno = i.interno
      JOIN establecimiento e ON i.codigo_establecimiento = e.codigo_establecimiento
      LEFT JOIN cups_cubiertos cc ON a.cup_codigo = cc.codigo
      WHERE a.fecha_autorizacion BETWEEN $1 AND $2
      ${esAdmin ? '' : "AND COALESCE(a.estado_autorizacion, 'pendiente') IN ('pendiente', 'autorizado')"}
      ORDER BY a.fecha_autorizacion DESC, a.numero_autorizacion DESC
      LIMIT $3 OFFSET $4
    `;

    const { rows } = await pool.query(sql, [fecha_inicio, fecha_fin, limit, offset]);
    res.json(rows);

  } catch (error) {
    console.error('Error en autorizaciones por fecha:', error);
    res.status(500).json({ error: 'Error en el servidor' });
  }
});

/**
 * GET /api/autorizaciones-estadisticas?fecha_inicio=YYYY-MM-DD&fecha_fin=YYYY-MM-DD
 * Devuelve conteos por estado y un histograma diario en el rango solicitado.
 */
app.get('/api/autorizaciones-estadisticas', verificarToken, esAdministrador, async (req, res) => {
  const hoy = new Date();
  const primerDiaMes = new Date(hoy.getFullYear(), hoy.getMonth(), 1);

  const rawInicio = req.query.fecha_inicio;
  const rawFin = req.query.fecha_fin;

  const inicio = rawInicio ? new Date(String(rawInicio)) : primerDiaMes;
  const fin = rawFin ? new Date(String(rawFin)) : hoy;

  if (Number.isNaN(inicio.getTime()) || Number.isNaN(fin.getTime())) {
    return res.status(400).json({ error: 'fecha_inicio o fecha_fin invalida' });
  }

  if (inicio > fin) {
    return res.status(400).json({ error: 'fecha_inicio no puede ser mayor que fecha_fin' });
  }

  const inicioStr = inicio.toISOString().slice(0, 10);
  const finStr = fin.toISOString().slice(0, 10);

  try {
    const resumenSql = `
      SELECT
        COUNT(*) FILTER (WHERE COALESCE(estado_autorizacion, 'pendiente') = 'autorizado') AS autorizadas,
        COUNT(*) FILTER (WHERE COALESCE(estado_autorizacion, 'pendiente') = 'no_autorizado') AS no_autorizadas,
        COUNT(*) FILTER (WHERE COALESCE(estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes,
        COUNT(*) AS total
      FROM autorizacion
      WHERE fecha_autorizacion BETWEEN $1::date AND $2::date;
    `;

    const diasSql = `
      WITH dias AS (
        SELECT generate_series($1::date, $2::date, interval '1 day')::date AS fecha
      )
      SELECT
        d.fecha,
        COUNT(a.numero_autorizacion) AS total,
        COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'autorizado') AS autorizadas,
        COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'no_autorizado') AS no_autorizadas,
        COUNT(*) FILTER (WHERE COALESCE(a.estado_autorizacion, 'pendiente') = 'pendiente') AS pendientes
      FROM dias d
      LEFT JOIN autorizacion a
        ON a.fecha_autorizacion::date = d.fecha
      GROUP BY d.fecha
      ORDER BY d.fecha;
    `;

    const resumenRes = await pool.query(resumenSql, [inicioStr, finStr]);
    const diasRes = await pool.query(diasSql, [inicioStr, finStr]);

    return res.json({
      rango: { inicio: inicioStr, fin: finStr },
      resumen: resumenRes.rows[0] || {
        autorizadas: 0,
        no_autorizadas: 0,
        pendientes: 0,
        total: 0,
      },
      dias: diasRes.rows || [],
    });
  } catch (error) {
    console.error('Error en /api/autorizaciones-estadisticas:', error.message);
    return res.status(500).json({ error: 'Error consultando estadisticas' });
  }
});

/**
 * PATCH /api/autorizaciones/estado-masivo
 * Body: { fecha_inicio, fecha_fin, estado_autorizacion, solo_pendientes? }
 * Solo administrador
 */
app.patch('/api/autorizaciones/estado-masivo', verificarToken, esAdministrador, async (req, res) => {
  const fecha_inicio = req.body?.fecha_inicio || req.query?.fecha_inicio;
  const fecha_fin = req.body?.fecha_fin || req.query?.fecha_fin;
  const estado = String(req.body?.estado_autorizacion || '')
    .trim()
    .toLowerCase();
  const soloPendientes = req.body?.solo_pendientes === true;

  if (!fecha_inicio || !fecha_fin) {
    return res.status(400).json({ error: 'fecha_inicio y fecha_fin son requeridos' });
  }

  const estadosPermitidos = ['pendiente', 'autorizado', 'no_autorizado'];
  if (!estadosPermitidos.includes(estado)) {
    return res.status(400).json({ error: 'estado_autorizacion invalido' });
  }

  try {
    const params = [estado, fecha_inicio, fecha_fin];
    let sql = `
      UPDATE autorizacion
      SET estado_autorizacion = $1
      WHERE fecha_autorizacion BETWEEN $2::date AND $3::date
    `;

    if (soloPendientes) {
      sql += ` AND COALESCE(estado_autorizacion, 'pendiente') = 'pendiente'`;
    }

    sql += ' RETURNING numero_autorizacion;';

    const result = await pool.query(sql, params);
    return res.json({
      mensaje: 'Estados actualizados',
      actualizados: result.rowCount || 0,
    });
  } catch (error) {
    console.error('Error en estado masivo:', error.message);
    return res.status(500).json({ error: 'Error actualizando estados' });
  }
});

/**
 * GET /api/autorizaciones-por-fecha/zip
 * Query params: fecha_inicio, fecha_fin
 * Devuelve un ZIP con todos los PDFs de las autorizaciones de ese rango
 * Solo para administradores
 */
app.get('/api/autorizaciones-por-fecha/zip', verificarToken, esAdministrador, crearJobZipHandler);

ensureCupsTables().catch((error) => {
  console.error('Error inicializando tablas CUPS:', error.message);
});

ensureAutorizacionVersionTables().catch((error) => {
  console.error('Error inicializando tablas de versiones:', error.message);
});

ensureIpsConvenio().catch((error) => {
  console.error('Error inicializando convenio de IPS:', error.message);
});

ensureProfesionalRepsTable().catch((error) => {
  console.error('Error inicializando tabla profesional_reps:', error.message);
});

ensureAutorizacionEstado().catch((error) => {
  console.error('Error inicializando estado de autorizaciones:', error.message);
});

ensureUsuarioTokenVersion().catch((error) => {
  console.error('Error inicializando token_version de usuario:', error.message);
});

ensureAdminFromEnv().catch((error) => {
  console.warn('Error asegurando admin desde .env:', error.message);
});


// --- 3. INICIO DEL SERVIDOR ---
app.listen(PORT, () => {
  console.log(`API escuchando en http://localhost:${PORT}`);
});
