const express = require('express'); const router = express.Router(); const { isAgent } = require('../middleware/auth'); const Ticket = require('../models/Ticket'); const Department = require('../models/Department'); const TicketVisita = require('../models/TicketVisita'); const TareasHubService = require('../config/tareasHubService'); router.get('/dashboard', isAgent, async (req, res) => { try { let tickets; if (['superadmin', 'admin'].includes(req.session.user.rol)) { tickets = await Ticket.findAll(req.session.user.company_id); } else { tickets = await Ticket.findByAgent(req.session.user.company_id, req.session.user.id); } const stats = await Ticket.countByStatus(req.session.user.company_id); const departments = await Department.findAll(req.session.user.company_id); res.render('agent/dashboard', { tickets, stats, departments }); } catch (error) { console.error(error); req.flash('error_msg', 'Error al cargar dashboard'); res.redirect('/'); } }); router.get('/ticket/:id', isAgent, async (req, res) => { try { const ticket = await Ticket.findById(req.params.id); if (!ticket) { req.flash('error_msg', 'Ticket no encontrado'); return res.redirect('/agent/dashboard'); } if (ticket.company_id !== req.session.user.company_id && req.session.user.rol !== 'superadmin') { req.flash('error_msg', 'Acceso denegado'); return res.redirect('/agent/dashboard'); } const respuestas = await Ticket.getResponses(req.params.id); let agents = []; if (['superadmin', 'admin'].includes(req.session.user.rol)) { const User = require('../models/User'); const allUsers = await User.findAll(req.session.user.company_id); agents = allUsers.filter(u => u.rol === 'agent'); } const visita = await TicketVisita.findByTicket(ticket.company_id, req.params.id); if (visita && visita.historial_json && typeof visita.historial_json === 'string') { try { visita.historial = JSON.parse(visita.historial_json); } catch (e) { visita.historial = null; } } else if (visita && visita.historial_json && typeof visita.historial_json === 'object') { visita.historial = visita.historial_json; } const tecnicosHub = await TareasHubService.obtenerTecnicos(); res.render('agent/view-ticket', { ticket, respuestas, agents, visita, tecnicosHub }); } catch (error) { console.error(error); req.flash('error_msg', 'Error al cargar ticket'); res.redirect('/agent/dashboard'); } }); router.post('/ticket/:id/respond', isAgent, async (req, res) => { const { mensaje } = req.body; if (!mensaje) { req.flash('error_msg', 'El mensaje es obligatorio'); return res.redirect(`/agent/ticket/${req.params.id}`); } try { await Ticket.addResponse(req.params.id, req.session.user.id, mensaje); req.flash('success_msg', 'Respuesta enviada'); res.redirect(`/agent/ticket/${req.params.id}`); } catch (error) { console.error(error); req.flash('error_msg', 'Error al enviar respuesta'); res.redirect(`/agent/ticket/${req.params.id}`); } }); router.post('/ticket/:id/status', isAgent, async (req, res) => { const { status, assigned_to } = req.body; try { const data = {}; if (status) data.status = status; if (assigned_to && ['superadmin', 'admin'].includes(req.session.user.rol)) { data.assigned_to = assigned_to; } await Ticket.update(req.params.id, data); req.flash('success_msg', 'Ticket actualizado'); res.redirect(`/agent/ticket/${req.params.id}`); } catch (error) { console.error(error); req.flash('error_msg', 'Error al actualizar ticket'); res.redirect(`/agent/ticket/${req.params.id}`); } }); router.post('/ticket/:id/assign', isAgent, async (req, res) => { if (!['superadmin', 'admin'].includes(req.session.user.rol)) { req.flash('error_msg', 'Acceso denegado'); return res.redirect('/agent/dashboard'); } const { assigned_to } = req.body; try { await Ticket.update(req.params.id, { assigned_to: assigned_to || null }); if (assigned_to) { await Ticket.update(req.params.id, { status: 'en_progreso' }); } req.flash('success_msg', 'Ticket asignado'); res.redirect(`/agent/ticket/${req.params.id}`); } catch (error) { console.error(error); req.flash('error_msg', 'Error al asignar ticket'); res.redirect(`/agent/ticket/${req.params.id}`); } }); // Programar Visita Técnica hacia tareas.telcotronics.com (FSM / Hub) router.post('/ticket/:id/visita', isAgent, async (req, res) => { try { const ticket = await Ticket.findById(req.params.id); if (!ticket || (ticket.company_id !== req.session.user.company_id && req.session.user.rol !== 'superadmin')) { req.flash('error_msg', 'Ticket no encontrado o sin permisos'); return res.redirect('/agent/dashboard'); } const { emailTecnico, tecnicoNombre, titulo, descripcion, ubicacion, coordenadas, referencia, fechaVisita, fechaFin, prioridad, checklist_items } = req.body; if (!emailTecnico) { req.flash('error_msg', 'Debe seleccionar un técnico responsable para la visita'); return res.redirect(`/agent/ticket/${req.params.id}`); } // Procesar checklist let checklistArr = []; if (typeof checklist_items === 'string') { checklistArr = checklist_items.split('\n').map(s => s.trim()).filter(Boolean); } else if (Array.isArray(checklist_items)) { checklistArr = checklist_items.filter(Boolean); } if (checklistArr.length === 0) { checklistArr = [ 'Revisión física de acometida y equipamiento', 'Verificación de conectividad y potencia', 'Pruebas operativas con el cliente y firma' ]; } const tituloVisita = titulo || `Visita Técnica: ${ticket.asunto} (Ticket #${ticket.id})`; const descVisita = descripcion || ticket.mensaje || 'Atención técnica presencial solicitada'; // 1. Crear en tareas.telcotronics.com const hubRes = await TareasHubService.crearVisita({ ticketId: ticket.id, titulo: tituloVisita, descripcion: descVisita, emailTecnico, fechaVisita: fechaVisita || null, fechaFin: fechaFin || null, ubicacion: ubicacion || '', coordenadas: coordenadas || '', referencia: referencia || '', prioridad: prioridad || 'Media', checklist: checklistArr }); // 2. Guardar en MariaDB ticket_visitas await TicketVisita.create({ company_id: ticket.company_id, ticket_id: ticket.id, external_id: hubRes.tareaId, tecnico_email: emailTecnico, tecnico_nombre: tecnicoNombre || emailTecnico, titulo: tituloVisita, descripcion: descVisita, ubicacion: ubicacion || '', fecha_visita: fechaVisita ? new Date(fechaVisita) : new Date(), fecha_fin: fechaFin ? new Date(fechaFin) : null, estado: 'programada', checklist_json: checklistArr, historial_json: hubRes.tarea || null }); // 3. Actualizar estado del ticket a 'en_progreso' await Ticket.update(ticket.id, { status: 'en_progreso' }); // 4. Registrar respuesta en el hilo del ticket const fechaStr = fechaVisita ? new Date(fechaVisita).toLocaleString('es-EC') : 'A coordinar'; const msgHilo = `📅 **Visita técnica programada**: Se ha asignado al técnico **${tecnicoNombre || emailTecnico}** para el **${fechaStr}**.\n\n📍 **Ubicación:** ${ubicacion || 'En sitio del cliente'}${referencia ? ` (${referencia})` : ''}\n\n📋 **Checklist operativo inicial:**\n${checklistArr.map(c => `- ${c}`).join('\n')}\n\n*ID de tarea en Hub:* \`${hubRes.tareaId}\``; await Ticket.addResponse(ticket.id, req.session.user.id, msgHilo); req.flash('success_msg', 'Visita técnica programada y sincronizada exitosamente con tareas.telcotronics.com'); res.redirect(`/agent/ticket/${ticket.id}`); } catch (error) { console.error('Error al programar visita técnica:', error); req.flash('error_msg', 'Error al programar visita técnica: ' + error.message); res.redirect(`/agent/ticket/${req.params.id}`); } }); // Sincronizar Estado y Timeline desde tareas.telcotronics.com router.post('/ticket/:id/visita/sync', isAgent, async (req, res) => { try { const ticket = await Ticket.findById(req.params.id); if (!ticket || (ticket.company_id !== req.session.user.company_id && req.session.user.rol !== 'superadmin')) { req.flash('error_msg', 'Ticket no encontrado'); return res.redirect('/agent/dashboard'); } const visita = await TicketVisita.findByTicket(ticket.company_id, ticket.id); if (!visita || !visita.external_id) { req.flash('error_msg', 'No hay visita técnica activa vinculada con Hub'); return res.redirect(`/agent/ticket/${ticket.id}`); } const hubData = await TareasHubService.consultarEstadoVisita(visita.external_id); const tarea = hubData.tarea; // Mapear estado let nuevoEstado = visita.estado; if (tarea.estado === 'Completado' || tarea.estado === 'Finalizada') { nuevoEstado = 'completada'; } else if (tarea.checkIn && tarea.checkIn.fecha) { nuevoEstado = 'en_sitio'; } else if (tarea.estado === 'En progreso') { nuevoEstado = 'en_camino'; } const estadoAnterior = visita.estado; await TicketVisita.update(visita.id, { estado: nuevoEstado, historial_json: tarea }); // Automatización bidireccional si se completó la visita if (nuevoEstado === 'completada' && estadoAnterior !== 'completada') { const observacion = tarea.observacion || 'Visita finalizada satisfactoriamente en terreno.'; const progreso = tarea.progreso_checklist; const firmante = (tarea.firma_cliente && tarea.firma_cliente.firmado) ? `\n✍️ *Firmado por:* ${tarea.firma_cliente.nombreFirmante} (${tarea.firma_cliente.cedulaFirmante || 'Cliente'})` : ''; const msgCierre = `✅ **Visita técnica completada en terreno** por **${visita.tecnico_nombre}**.\n\n📝 **Observaciones de cierre:** ${observacion}\n📊 **Checklist completado:** ${progreso?.completados || 0}/${progreso?.total || 0} tareas.${firmante}`; await Ticket.addResponse(ticket.id, req.session.user.id, msgCierre); } req.flash('success_msg', 'Estado de la visita técnica sincronizado con el Hub'); res.redirect(`/agent/ticket/${ticket.id}`); } catch (error) { console.error('Error sincronizando visita:', error); req.flash('error_msg', 'Error al sincronizar con Hub: ' + error.message); res.redirect(`/agent/ticket/${req.params.id}`); } }); module.exports = router;