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;