feat(fsm): modulo de asignacion de tareas y visitas tecnicas integrado con tareas.telcotronics.com

This commit is contained in:
2026-10-17 03:06:16 -05:00
parent 0f2708462e
commit 3afcff699a
9 changed files with 802 additions and 8 deletions
+29
View File
@@ -90,6 +90,35 @@ async function setup() {
await conn.query('ALTER TABLE tickets ADD FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE');
} catch (e) { if (e.errno !== 1826 && e.errno !== 1005) throw e; }
// Create ticket_visitas table
await conn.query(`
CREATE TABLE IF NOT EXISTS ticket_visitas (
id INT AUTO_INCREMENT PRIMARY KEY,
company_id INT NOT NULL,
ticket_id INT NOT NULL,
external_id VARCHAR(100) NULL,
tecnico_email VARCHAR(150) NOT NULL,
tecnico_nombre VARCHAR(150) NULL,
titulo VARCHAR(255) NOT NULL,
descripcion TEXT NULL,
ubicacion VARCHAR(255) NULL,
latitud DECIMAL(10, 7) NULL,
longitud DECIMAL(10, 7) NULL,
fecha_visita DATETIME NULL,
fecha_fin DATETIME NULL,
estado ENUM('programada', 'en_camino', 'en_sitio', 'completada', 'cancelada') DEFAULT 'programada',
checklist_json TEXT NULL,
historial_json LONGTEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (ticket_id),
INDEX (company_id),
INDEX (external_id),
FOREIGN KEY (ticket_id) REFERENCES tickets(id) ON DELETE CASCADE,
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Seed departments
const [depts] = await conn.query('SELECT COUNT(*) as c FROM departments WHERE company_id = ?', [companyId]);
if (depts[0].c === 0) {
+139
View File
@@ -0,0 +1,139 @@
/**
* tareasHubService.js
* Servicio de integración con el sistema de Tareas y Gestión de Campo (FSM) de Telcotronics.
* Conecta con https://tareas.telcotronics.com/api/external
*/
const HUB_API_URL = process.env.HUB_API_URL || 'https://tareas.telcotronics.com/api/external';
const HUB_API_KEY = process.env.HUB_API_KEY || 'sigma_crm_test_key_123';
class TareasHubService {
/**
* Obtiene el listado de técnicos/usuarios disponibles en tareas.telcotronics.com
*/
static async obtenerTecnicos() {
try {
const url = `${HUB_API_URL}/users/list`;
const res = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'x-api-key': HUB_API_KEY
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: Fallo al consultar técnicos en Hub`);
}
const data = await res.json();
return data.usuarios || [];
} catch (error) {
console.error('[TareasHubService.obtenerTecnicos]:', error.message);
return [];
}
}
/**
* Programa una visita técnica / tarea operativa con trazabilidad de campo (FSM)
*/
static async crearVisita({
ticketId,
titulo,
descripcion,
emailTecnico,
fechaVisita = null,
fechaFin = null,
ubicacion = '',
coordenadas = null,
referencia = '',
prioridad = 'Media',
checklist = []
}) {
try {
const url = `${HUB_API_URL}/tareas`;
let lat = null;
let lng = null;
if (coordenadas && typeof coordenadas === 'string' && coordenadas.includes(',')) {
const parts = coordenadas.split(',');
lat = parseFloat(parts[0].trim()) || null;
lng = parseFloat(parts[1].trim()) || null;
}
const payload = {
titulo: titulo || `Visita Técnica - Ticket #${ticketId}`,
descripcion: descripcion || `Visita técnica generada desde Help Desk para el Ticket #${ticketId}`,
emailTecnico: emailTecnico,
prioridad: prioridad,
ticket_id: String(ticketId),
origen: 'HELP_DESK',
origen_externo_id: String(ticketId),
fechaInicio: fechaVisita ? new Date(fechaVisita).toISOString() : new Date().toISOString(),
fechaLimite: fechaFin ? new Date(fechaFin).toISOString() : (fechaVisita ? new Date(fechaVisita).toISOString() : null),
ubicacion: ubicacion,
destino: {
direccion: ubicacion,
latitud: lat,
longitud: lng,
referencia: referencia
},
checklist_inicial: Array.isArray(checklist) && checklist.length > 0
? checklist
: [
'Revisión física de acometida y equipamiento',
'Verificación de conectividad y potencia',
'Pruebas operativas con el cliente y firma'
]
};
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-api-key': HUB_API_KEY
},
body: JSON.stringify(payload)
});
const data = await res.json();
if (!res.ok || !data.success) {
throw new Error(data.mensaje || data.error || `HTTP ${res.status} al crear tarea en Hub`);
}
return data;
} catch (error) {
console.error('[TareasHubService.crearVisita]:', error.message);
throw error;
}
}
/**
* Consulta el estado en tiempo real y el timeline operativo de la tarea/visita en el Hub
*/
static async consultarEstadoVisita(externalIdOrTicketId) {
try {
const url = `${HUB_API_URL}/tareas/${externalIdOrTicketId}/status`;
const res = await fetch(url, {
method: 'GET',
headers: {
'Accept': 'application/json',
'x-api-key': HUB_API_KEY
}
});
if (!res.ok) {
throw new Error(`HTTP ${res.status}: No se pudo obtener el estado de la tarea`);
}
const data = await res.json();
return data;
} catch (error) {
console.error('[TareasHubService.consultarEstadoVisita]:', error.message);
throw error;
}
}
}
module.exports = TareasHubService;