feat(fsm): implementar gestion y asignacion de sets de herramientas a tecnicos

This commit is contained in:
2026-10-31 17:07:30 -05:00
parent 06ef042538
commit c07162c375
13 changed files with 1793 additions and 9 deletions
+148
View File
@@ -155,6 +155,8 @@ async function setup() {
estado ENUM('programada', 'en_camino', 'en_sitio', 'completada', 'cancelada') DEFAULT 'programada',
checklist_json TEXT NULL,
historial_json LONGTEXT NULL,
set_herramientas_id INT NULL,
set_herramientas_nombre VARCHAR(150) NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (ticket_id),
@@ -165,6 +167,152 @@ async function setup() {
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
await migrateColumn('ticket_visitas', 'set_herramientas_id', 'set_herramientas_id INT NULL');
await migrateColumn('ticket_visitas', 'set_herramientas_nombre', 'set_herramientas_nombre VARCHAR(150) NULL');
// Create herramientas (Catálogo detallado de herramientas físicas e instrumentos)
await conn.query(`
CREATE TABLE IF NOT EXISTS herramientas (
id INT AUTO_INCREMENT PRIMARY KEY,
company_id INT NOT NULL,
nombre VARCHAR(150) NOT NULL,
codigo VARCHAR(50) NULL,
numero_serie VARCHAR(100) NULL,
categoria VARCHAR(100) DEFAULT 'general',
marca VARCHAR(100) NULL,
modelo VARCHAR(100) NULL,
estado ENUM('operativo', 'en_mantenimiento', 'danado', 'baja') DEFAULT 'operativo',
ubicacion_bodega VARCHAR(150) NULL,
notas TEXT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (company_id),
INDEX (codigo),
INDEX (estado),
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Create herramientas_sets (Agrupaciones lógicas / Kits de herramientas reutilizables)
await conn.query(`
CREATE TABLE IF NOT EXISTS herramientas_sets (
id INT AUTO_INCREMENT PRIMARY KEY,
company_id INT NOT NULL,
nombre VARCHAR(150) NOT NULL,
codigo VARCHAR(50) NULL,
descripcion TEXT NULL,
categoria VARCHAR(100) DEFAULT 'general',
is_active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (company_id),
INDEX (codigo),
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Create herramientas_set_items (Herramientas incluidas en cada Set/Kit)
await conn.query(`
CREATE TABLE IF NOT EXISTS herramientas_set_items (
id INT AUTO_INCREMENT PRIMARY KEY,
set_id INT NOT NULL,
herramienta_id INT NOT NULL,
cantidad INT DEFAULT 1,
FOREIGN KEY (set_id) REFERENCES herramientas_sets(id) ON DELETE CASCADE,
FOREIGN KEY (herramienta_id) REFERENCES herramientas(id) ON DELETE CASCADE,
UNIQUE KEY idx_set_herramienta (set_id, herramienta_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Create tecnico_herramientas_asignaciones (Asignación mixta: Dotación fija o por Visita FSM)
await conn.query(`
CREATE TABLE IF NOT EXISTS tecnico_herramientas_asignaciones (
id INT AUTO_INCREMENT PRIMARY KEY,
company_id INT NOT NULL,
tecnico_id INT NOT NULL,
set_id INT NULL,
herramienta_id INT NULL,
ticket_visita_id INT NULL,
tipo_asignacion ENUM('dotacion_fija', 'visita_fsm') DEFAULT 'dotacion_fija',
fecha_asignacion DATETIME DEFAULT CURRENT_TIMESTAMP,
fecha_devolucion DATETIME NULL,
estado ENUM('asignado', 'devuelto', 'observado') DEFAULT 'asignado',
observaciones_entrega TEXT NULL,
observaciones_devolucion TEXT NULL,
asignado_por INT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
INDEX (company_id),
INDEX (tecnico_id),
INDEX (ticket_visita_id),
FOREIGN KEY (company_id) REFERENCES companies(id) ON DELETE CASCADE,
FOREIGN KEY (tecnico_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (set_id) REFERENCES herramientas_sets(id) ON DELETE SET NULL,
FOREIGN KEY (herramienta_id) REFERENCES herramientas(id) ON DELETE SET NULL,
FOREIGN KEY (ticket_visita_id) REFERENCES ticket_visitas(id) ON DELETE SET NULL,
FOREIGN KEY (asignado_por) REFERENCES users(id) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
`);
// Seed herramientas y sets iniciales para Telcotronics si no existen
const [existingTools] = await conn.query('SELECT COUNT(*) as c FROM herramientas WHERE company_id = 1');
if (existingTools[0].c === 0) {
console.log('+ Sembrando catálogo de herramientas para Telcotronics...');
const initialTools = [
[1, 'Fusionadora de Fibra Óptica', 'TOOL-FIB-01', 'SN-FIB7021', 'fibra_optica', 'Fujikura', '70S', 'operativo', 'Bodega Central - Estante A1'],
[1, 'OTDR Mini Pro de Precisión', 'TOOL-OTDR-01', 'SN-OTDR4402', 'medicion', 'Anritsu', 'MT9090', 'operativo', 'Bodega Central - Estante A1'],
[1, 'Crimpadora RJ45/RJ11 Pro', 'TOOL-CRIMP-01', 'SN-CRIMP-88', 'redes', 'Klein Tools', 'VDV226', 'operativo', 'Bodega Central - Caja 3'],
[1, 'Tester Certificador Cable de Red PoE', 'TOOL-TEST-01', 'SN-FLK8891', 'medicion', 'Fluke Networks', 'MicroScanner', 'operativo', 'Bodega Central - Estante B2'],
[1, 'Monitor Portátil de Prueba CCTV AHD/IP', 'TOOL-CCTV-01', 'SN-HK9012', 'cctv', 'Hikvision', 'TestScreen Pro 4K', 'operativo', 'Bodega Central - Estante C1'],
[1, 'Taladro Percutor Inalámbrico 20V Max', 'TOOL-TAL-01', 'SN-DW2019', 'mano', 'DeWalt', 'DCD778', 'operativo', 'Bodega Central - Gaveta 5'],
[1, 'Multímetro Digital True RMS', 'TOOL-MULT-01', 'SN-FLK1174', 'electricidad', 'Fluke', '117 Electrician', 'operativo', 'Bodega Central - Estante B2'],
[1, 'Escalera Telescópica de Aluminio 3.8m', 'TOOL-ESC-01', 'SN-ESC-901', 'mano', 'Werner', 'Telescop 3.8', 'operativo', 'Área de Vehículos']
];
for (const t of initialTools) {
await conn.query(
`INSERT INTO herramientas (company_id, nombre, codigo, numero_serie, categoria, marca, modelo, estado, ubicacion_bodega)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
t
);
}
// Crear sets
console.log('+ Sembrando Sets / Kits de Herramientas para Telcotronics...');
const [set1] = await conn.query(
`INSERT INTO herramientas_sets (company_id, nombre, codigo, descripcion, categoria)
VALUES (1, 'Kit Fibra Óptica & Enlaces', 'SET-FIB-PRO', 'Kit completo para tendido, fusión y certificación de enlaces de fibra óptica.', 'fibra_optica')`
);
const [set2] = await conn.query(
`INSERT INTO herramientas_sets (company_id, nombre, codigo, descripcion, categoria)
VALUES (1, 'Kit Seguridad Electrónica & CCTV', 'SET-CCTV-01', 'Herramientas indispensables para montaje, alineación y pruebas de cámaras IP y analógicas.', 'cctv')`
);
const [set3] = await conn.query(
`INSERT INTO herramientas_sets (company_id, nombre, codigo, descripcion, categoria)
VALUES (1, 'Kit Redes & Cableado Estructurado', 'SET-RED-01', 'Instrumentos para certificación de puntos de red, crimpado y pruebas PoE.', 'redes')`
);
// Asociar herramientas a los sets
const [toolsRows] = await conn.query('SELECT id, codigo FROM herramientas WHERE company_id = 1');
const toolMap = {};
toolsRows.forEach(r => { toolMap[r.codigo] = r.id; });
// Set 1 items: Fusionadora + OTDR + Crimpadora
if (toolMap['TOOL-FIB-01'] && toolMap['TOOL-OTDR-01']) {
await conn.query('INSERT INTO herramientas_set_items (set_id, herramienta_id, cantidad) VALUES (?, ?, 1), (?, ?, 1)',
[set1.insertId, toolMap['TOOL-FIB-01'], set1.insertId, toolMap['TOOL-OTDR-01']]);
}
// Set 2 items: Monitor CCTV + Taladro + Multímetro
if (toolMap['TOOL-CCTV-01'] && toolMap['TOOL-TAL-01'] && toolMap['TOOL-MULT-01']) {
await conn.query('INSERT INTO herramientas_set_items (set_id, herramienta_id, cantidad) VALUES (?, ?, 1), (?, ?, 1), (?, ?, 1)',
[set2.insertId, toolMap['TOOL-CCTV-01'], set2.insertId, toolMap['TOOL-TAL-01'], set2.insertId, toolMap['TOOL-MULT-01']]);
}
// Set 3 items: Tester Fluke + Crimpadora + Multímetro
if (toolMap['TOOL-TEST-01'] && toolMap['TOOL-CRIMP-01'] && toolMap['TOOL-MULT-01']) {
await conn.query('INSERT INTO herramientas_set_items (set_id, herramienta_id, cantidad) VALUES (?, ?, 1), (?, ?, 1), (?, ?, 1)',
[set3.insertId, toolMap['TOOL-TEST-01'], set3.insertId, toolMap['TOOL-CRIMP-01'], set3.insertId, toolMap['TOOL-MULT-01']]);
}
}
// Seed departments for all ecosystem companies
for (const ec of ecosystemCompanies) {
const [depts] = await conn.query('SELECT COUNT(*) as c FROM departments WHERE company_id = ?', [ec.id]);