275 lines
12 KiB
JavaScript
275 lines
12 KiB
JavaScript
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;
|