449 lines
17 KiB
JavaScript
449 lines
17 KiB
JavaScript
const express = require('express');
|
|
const router = express.Router();
|
|
const bcrypt = require('bcryptjs');
|
|
const { isCompanyAdmin } = require('../middleware/auth');
|
|
const User = require('../models/User');
|
|
const Department = require('../models/Department');
|
|
const Ticket = require('../models/Ticket');
|
|
const Herramienta = require('../models/Herramienta');
|
|
const HerramientaSet = require('../models/HerramientaSet');
|
|
const HerramientaAsignacion = require('../models/HerramientaAsignacion');
|
|
const { guardarFoto } = require('../config/uploader');
|
|
const db = require('../config/db');
|
|
const QRCode = require('qrcode');
|
|
|
|
router.get('/dashboard', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const users = await User.findAll(req.session.user.company_id);
|
|
const tickets = await Ticket.findAll(req.session.user.company_id);
|
|
const departments = await Department.findAll(req.session.user.company_id);
|
|
const stats = await Ticket.countByStatus(req.session.user.company_id);
|
|
res.render('admin/dashboard', { users, tickets, departments, stats });
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al cargar dashboard');
|
|
res.redirect('/');
|
|
}
|
|
});
|
|
|
|
router.get('/users', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const users = await User.findAll(req.session.user.company_id);
|
|
res.render('admin/users', { users });
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al cargar usuarios');
|
|
res.redirect('/admin/dashboard');
|
|
}
|
|
});
|
|
|
|
router.get('/users/create', isCompanyAdmin, (req, res) => {
|
|
res.render('admin/create-user');
|
|
});
|
|
|
|
router.post('/users/create', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, email, password, rol } = req.body;
|
|
if (!nombre || !email || !password) {
|
|
req.flash('error_msg', 'Todos los campos son obligatorios');
|
|
return res.redirect('/admin/users/create');
|
|
}
|
|
try {
|
|
const existing = await User.findByEmail(email);
|
|
if (existing) {
|
|
req.flash('error_msg', 'El email ya existe');
|
|
return res.redirect('/admin/users/create');
|
|
}
|
|
const hashedPassword = await bcrypt.hash(password, 10);
|
|
const userId = await User.create(req.session.user.company_id, nombre, email, hashedPassword, rol || 'client');
|
|
req.flash('success_msg', 'Usuario creado exitosamente');
|
|
res.redirect('/admin/users');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al crear usuario');
|
|
res.redirect('/admin/users/create');
|
|
}
|
|
});
|
|
|
|
router.post('/users/invite/:id', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const user = await User.findById(req.params.id);
|
|
if (!user || user.company_id !== req.session.user.company_id) {
|
|
req.flash('error_msg', 'Usuario no encontrado');
|
|
return res.redirect('/admin/users');
|
|
}
|
|
const token = await User.generateInviteToken(user.id);
|
|
const inviteLink = `${req.protocol}://${req.get('host')}/invite/${token}`;
|
|
req.flash('success_msg', `Link de invitación: ${inviteLink}`);
|
|
res.redirect('/admin/users');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al generar invitación');
|
|
res.redirect('/admin/users');
|
|
}
|
|
});
|
|
|
|
router.post('/users/delete/:id', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const user = await User.findById(req.params.id);
|
|
if (!user || user.company_id !== req.session.user.company_id) {
|
|
req.flash('error_msg', 'Usuario no encontrado');
|
|
return res.redirect('/admin/users');
|
|
}
|
|
if (user.rol === 'superadmin') {
|
|
req.flash('error_msg', 'No puedes eliminar al superadmin');
|
|
return res.redirect('/admin/users');
|
|
}
|
|
await User.delete(req.params.id);
|
|
req.flash('success_msg', 'Usuario eliminado');
|
|
res.redirect('/admin/users');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al eliminar usuario');
|
|
res.redirect('/admin/users');
|
|
}
|
|
});
|
|
|
|
router.get('/departments', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const departments = await Department.findAll(req.session.user.company_id);
|
|
res.render('admin/departments', { departments });
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al cargar departamentos');
|
|
res.redirect('/admin/dashboard');
|
|
}
|
|
});
|
|
|
|
router.post('/departments/create', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, descripcion } = req.body;
|
|
if (!nombre) {
|
|
req.flash('error_msg', 'El nombre es obligatorio');
|
|
return res.redirect('/admin/departments');
|
|
}
|
|
try {
|
|
await Department.create(req.session.user.company_id, nombre, descripcion || '');
|
|
req.flash('success_msg', 'Departamento creado');
|
|
res.redirect('/admin/departments');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al crear departamento');
|
|
res.redirect('/admin/departments');
|
|
}
|
|
});
|
|
|
|
router.post('/departments/update/:id', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, descripcion } = req.body;
|
|
try {
|
|
await Department.update(req.params.id, nombre, descripcion || '');
|
|
req.flash('success_msg', 'Departamento actualizado');
|
|
res.redirect('/admin/departments');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al actualizar departamento');
|
|
res.redirect('/admin/departments');
|
|
}
|
|
});
|
|
|
|
router.post('/departments/delete/:id', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
await Department.delete(req.params.id);
|
|
req.flash('success_msg', 'Departamento eliminado');
|
|
res.redirect('/admin/departments');
|
|
} catch (error) {
|
|
console.error(error);
|
|
req.flash('error_msg', 'Error al eliminar departamento');
|
|
res.redirect('/admin/departments');
|
|
}
|
|
});
|
|
|
|
// ==========================================
|
|
// GESTIÓN DE HERRAMIENTAS Y SETS / KITS FSM
|
|
// ==========================================
|
|
|
|
router.get('/herramientas', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const companyId = req.session.user.company_id;
|
|
const [herramientas, sets, asignaciones, stats, allUsers] = await Promise.all([
|
|
Herramienta.findAll(companyId),
|
|
HerramientaSet.findAll(companyId),
|
|
HerramientaAsignacion.findAll(companyId),
|
|
Herramienta.countByEstado(companyId),
|
|
User.findAll(companyId)
|
|
]);
|
|
const tecnicos = allUsers.filter(u => u.rol === 'agent' || u.rol === 'admin');
|
|
|
|
res.render('admin/herramientas', {
|
|
title: 'Herramientas & Sets',
|
|
herramientas,
|
|
sets,
|
|
asignaciones,
|
|
stats,
|
|
tecnicos
|
|
});
|
|
} catch (error) {
|
|
console.error('Error al cargar herramientas:', error);
|
|
req.flash('error_msg', 'Error al cargar gestión de herramientas');
|
|
res.redirect('/admin/dashboard');
|
|
}
|
|
});
|
|
|
|
// Crear Herramienta Individual
|
|
router.post('/herramientas/create', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, codigo, numero_serie, categoria, marca, modelo, estado, ubicacion_bodega, notas } = req.body;
|
|
if (!nombre) {
|
|
req.flash('error_msg', 'El nombre de la herramienta es obligatorio');
|
|
return res.redirect('/admin/herramientas');
|
|
}
|
|
try {
|
|
let foto_url = null;
|
|
if (req.files && req.files.foto) {
|
|
foto_url = await guardarFoto(req.files.foto, 'herramienta');
|
|
}
|
|
|
|
await Herramienta.create({
|
|
company_id: req.session.user.company_id,
|
|
nombre,
|
|
codigo: codigo || null,
|
|
numero_serie: numero_serie || null,
|
|
categoria: categoria || 'general',
|
|
marca: marca || null,
|
|
modelo: modelo || null,
|
|
estado: estado || 'operativo',
|
|
ubicacion_bodega: ubicacion_bodega || null,
|
|
notas: notas || null,
|
|
foto_url
|
|
});
|
|
req.flash('success_msg', `Herramienta "${nombre}" registrada correctamente`);
|
|
res.redirect('/admin/herramientas#tab-herramientas');
|
|
} catch (error) {
|
|
console.error('Error al crear herramienta:', error);
|
|
req.flash('error_msg', 'Error al registrar la herramienta: ' + error.message);
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Editar Herramienta Individual
|
|
router.post('/herramientas/edit/:id', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, codigo, numero_serie, categoria, marca, modelo, estado, ubicacion_bodega, notas } = req.body;
|
|
try {
|
|
const updateData = {
|
|
nombre,
|
|
codigo,
|
|
numero_serie,
|
|
categoria,
|
|
marca,
|
|
modelo,
|
|
estado,
|
|
ubicacion_bodega,
|
|
notas
|
|
};
|
|
|
|
if (req.files && req.files.foto) {
|
|
updateData.foto_url = await guardarFoto(req.files.foto, 'herramienta');
|
|
}
|
|
|
|
await Herramienta.update(req.params.id, req.session.user.company_id, updateData);
|
|
req.flash('success_msg', 'Herramienta actualizada exitosamente');
|
|
res.redirect('/admin/herramientas#tab-herramientas');
|
|
} catch (error) {
|
|
console.error('Error al actualizar herramienta:', error);
|
|
req.flash('error_msg', 'Error al actualizar herramienta: ' + error.message);
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Eliminar Herramienta Individual
|
|
router.post('/herramientas/delete/:id', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
await Herramienta.delete(req.params.id, req.session.user.company_id);
|
|
req.flash('success_msg', 'Herramienta eliminada');
|
|
res.redirect('/admin/herramientas#tab-herramientas');
|
|
} catch (error) {
|
|
console.error('Error al eliminar herramienta:', error);
|
|
req.flash('error_msg', 'Error al eliminar herramienta');
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Crear Set / Kit de Herramientas
|
|
router.post('/herramientas/sets/create', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, codigo, descripcion, categoria, herramientas_ids } = req.body;
|
|
if (!nombre) {
|
|
req.flash('error_msg', 'El nombre del Set / Kit es obligatorio');
|
|
return res.redirect('/admin/herramientas');
|
|
}
|
|
try {
|
|
let items = [];
|
|
if (Array.isArray(herramientas_ids)) {
|
|
items = herramientas_ids;
|
|
} else if (herramientas_ids) {
|
|
items = [herramientas_ids];
|
|
}
|
|
|
|
let foto_url = null;
|
|
if (req.files && req.files.foto) {
|
|
foto_url = await guardarFoto(req.files.foto, 'kit');
|
|
}
|
|
|
|
await HerramientaSet.create({
|
|
company_id: req.session.user.company_id,
|
|
nombre,
|
|
codigo: codigo || null,
|
|
descripcion: descripcion || null,
|
|
categoria: categoria || 'general',
|
|
foto_url
|
|
}, items);
|
|
|
|
req.flash('success_msg', `Kit de herramientas "${nombre}" creado con ${items.length} instrumentos.`);
|
|
res.redirect('/admin/herramientas#tab-sets');
|
|
} catch (error) {
|
|
console.error('Error al crear set de herramientas:', error);
|
|
req.flash('error_msg', 'Error al crear set: ' + error.message);
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Editar Set / Kit de Herramientas
|
|
router.post('/herramientas/sets/edit/:id', isCompanyAdmin, async (req, res) => {
|
|
const { nombre, codigo, descripcion, categoria, is_active, herramientas_ids } = req.body;
|
|
try {
|
|
let items = null;
|
|
if (herramientas_ids !== undefined) {
|
|
items = Array.isArray(herramientas_ids) ? herramientas_ids : (herramientas_ids ? [herramientas_ids] : []);
|
|
}
|
|
|
|
const updateData = {
|
|
nombre,
|
|
codigo,
|
|
descripcion,
|
|
categoria,
|
|
is_active: is_active === '1' || is_active === 1 ? 1 : 0
|
|
};
|
|
|
|
if (req.files && req.files.foto) {
|
|
updateData.foto_url = await guardarFoto(req.files.foto, 'kit');
|
|
}
|
|
|
|
await HerramientaSet.update(req.params.id, req.session.user.company_id, updateData, items);
|
|
|
|
req.flash('success_msg', 'Set / Kit actualizado exitosamente');
|
|
res.redirect('/admin/herramientas#tab-sets');
|
|
} catch (error) {
|
|
console.error('Error al actualizar set:', error);
|
|
req.flash('error_msg', 'Error al actualizar set: ' + error.message);
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Eliminar Set / Kit de Herramientas
|
|
router.post('/herramientas/sets/delete/:id', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
await HerramientaSet.delete(req.params.id, req.session.user.company_id);
|
|
req.flash('success_msg', 'Set / Kit eliminado');
|
|
res.redirect('/admin/herramientas#tab-sets');
|
|
} catch (error) {
|
|
console.error('Error al eliminar set:', error);
|
|
req.flash('error_msg', 'Error al eliminar set');
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Asignar Set de Herramientas a Técnico (Dotación Fija)
|
|
router.post('/herramientas/asignar', isCompanyAdmin, async (req, res) => {
|
|
const { tecnico_id, set_id, herramienta_id, tipo_asignacion, observaciones_entrega } = req.body;
|
|
if (!tecnico_id || (!set_id && !herramienta_id)) {
|
|
req.flash('error_msg', 'Debe seleccionar un técnico y al menos un set o herramienta');
|
|
return res.redirect('/admin/herramientas');
|
|
}
|
|
try {
|
|
let foto_entrega_url = null;
|
|
if (req.files && req.files.foto_entrega) {
|
|
foto_entrega_url = await guardarFoto(req.files.foto_entrega, 'entrega');
|
|
}
|
|
|
|
await HerramientaAsignacion.create({
|
|
company_id: req.session.user.company_id,
|
|
tecnico_id: parseInt(tecnico_id),
|
|
set_id: set_id ? parseInt(set_id) : null,
|
|
herramienta_id: herramienta_id ? parseInt(herramienta_id) : null,
|
|
tipo_asignacion: tipo_asignacion || 'dotacion_fija',
|
|
observaciones_entrega: observaciones_entrega || 'Asignación administrativa de equipamiento',
|
|
foto_entrega_url,
|
|
asignado_por: req.session.user.id
|
|
});
|
|
req.flash('success_msg', 'Equipamiento asignado al técnico exitosamente');
|
|
res.redirect('/admin/herramientas#tab-asignaciones');
|
|
} catch (error) {
|
|
console.error('Error al asignar herramientas:', error);
|
|
req.flash('error_msg', 'Error al asignar equipamiento: ' + error.message);
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Registrar Devolución de Herramientas
|
|
router.post('/herramientas/devolver/:id', isCompanyAdmin, async (req, res) => {
|
|
const { observaciones_devolucion, estado } = req.body;
|
|
try {
|
|
let foto_devolucion_url = null;
|
|
if (req.files && req.files.foto_devolucion) {
|
|
foto_devolucion_url = await guardarFoto(req.files.foto_devolucion, 'devolucion');
|
|
}
|
|
|
|
await HerramientaAsignacion.devolver(req.params.id, req.session.user.company_id, {
|
|
observaciones_devolucion: observaciones_devolucion || 'Devuelto conforme a bodega',
|
|
estado: estado || 'devuelto',
|
|
foto_devolucion_url
|
|
});
|
|
req.flash('success_msg', 'Devolución de herramientas registrada correctamente');
|
|
res.redirect('/admin/herramientas#tab-asignaciones');
|
|
} catch (error) {
|
|
console.error('Error al registrar devolución:', error);
|
|
req.flash('error_msg', 'Error al registrar devolución');
|
|
res.redirect('/admin/herramientas');
|
|
}
|
|
});
|
|
|
|
// Imprimir Etiqueta QR de Set / Caja de Herramientas
|
|
router.get('/herramientas/sets/:id/etiqueta', isCompanyAdmin, async (req, res) => {
|
|
try {
|
|
const companyId = req.session.user.company_id;
|
|
const set = await HerramientaSet.findById(req.params.id, companyId);
|
|
if (!set) {
|
|
req.flash('error_msg', 'Kit de herramientas no encontrado');
|
|
return res.redirect('/admin/herramientas#tab-sets');
|
|
}
|
|
|
|
const proto = req.headers['x-forwarded-proto'] || req.protocol;
|
|
const host = req.headers['x-forwarded-host'] || req.get('host');
|
|
const baseUrl = process.env.APP_URL || `${proto}://${host}`;
|
|
const scanUrl = `${baseUrl}/qr/set/${set.qr_token}`;
|
|
|
|
const qrDataUrl = await QRCode.toDataURL(scanUrl, {
|
|
width: 260,
|
|
margin: 1,
|
|
color: {
|
|
dark: '#000000',
|
|
light: '#ffffff'
|
|
},
|
|
errorCorrectionLevel: 'H'
|
|
});
|
|
|
|
const [companyRows] = await db.query('SELECT name FROM companies WHERE id = ?', [companyId]);
|
|
set.company_name = companyRows[0]?.name || 'Telcotronics';
|
|
|
|
res.render('admin/etiqueta_qr', {
|
|
title: `Etiqueta QR: ${set.nombre}`,
|
|
set,
|
|
herramientas: set.herramientas || [],
|
|
scanUrl,
|
|
qrDataUrl
|
|
});
|
|
} catch (error) {
|
|
console.error('Error al generar etiqueta QR de kit:', error);
|
|
req.flash('error_msg', 'Error al generar etiqueta QR: ' + error.message);
|
|
res.redirect('/admin/herramientas#tab-sets');
|
|
}
|
|
});
|
|
|
|
module.exports = router;
|