feat(fsm): modulo de asignacion de tareas y visitas tecnicas integrado con tareas.telcotronics.com
This commit is contained in:
+169
-1
@@ -3,6 +3,8 @@ 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 {
|
||||
@@ -40,7 +42,21 @@ router.get('/ticket/:id', isAgent, async (req, res) => {
|
||||
const allUsers = await User.findAll(req.session.user.company_id);
|
||||
agents = allUsers.filter(u => u.rol === 'agent');
|
||||
}
|
||||
res.render('agent/view-ticket', { ticket, respuestas, agents });
|
||||
|
||||
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');
|
||||
@@ -103,4 +119,156 @@ router.post('/ticket/:id/assign', isAgent, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
Reference in New Issue
Block a user