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
+96
View File
@@ -0,0 +1,96 @@
const db = require('../config/db');
class Herramienta {
static async findAll(companyId, filtros = {}) {
let sql = 'SELECT * FROM herramientas WHERE company_id = ?';
const params = [companyId];
if (filtros.estado) {
sql += ' AND estado = ?';
params.push(filtros.estado);
}
if (filtros.categoria) {
sql += ' AND categoria = ?';
params.push(filtros.categoria);
}
if (filtros.buscar) {
sql += ' AND (nombre LIKE ? OR codigo LIKE ? OR numero_serie LIKE ? OR marca LIKE ?)';
const term = `%${filtros.buscar}%`;
params.push(term, term, term, term);
}
sql += ' ORDER BY id DESC';
const [rows] = await db.query(sql, params);
return rows;
}
static async findById(id, companyId) {
const [rows] = await db.query(
'SELECT * FROM herramientas WHERE id = ? AND company_id = ?',
[id, companyId]
);
return rows[0] || null;
}
static async create(data) {
const {
company_id,
nombre,
codigo = null,
numero_serie = null,
categoria = 'general',
marca = null,
modelo = null,
estado = 'operativo',
ubicacion_bodega = null,
notas = null
} = data;
const [result] = await db.query(
`INSERT INTO herramientas
(company_id, nombre, codigo, numero_serie, categoria, marca, modelo, estado, ubicacion_bodega, notas)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[company_id, nombre, codigo, numero_serie, categoria, marca, modelo, estado, ubicacion_bodega, notas]
);
return result.insertId;
}
static async update(id, companyId, data) {
const fields = [];
const values = [];
const allowed = ['nombre', 'codigo', 'numero_serie', 'categoria', 'marca', 'modelo', 'estado', 'ubicacion_bodega', 'notas'];
for (const [key, val] of Object.entries(data)) {
if (allowed.includes(key)) {
fields.push(`${key} = ?`);
values.push(val);
}
}
if (fields.length === 0) return;
values.push(id, companyId);
await db.query(`UPDATE herramientas SET ${fields.join(', ')} WHERE id = ? AND company_id = ?`, values);
}
static async delete(id, companyId) {
await db.query('DELETE FROM herramientas WHERE id = ? AND company_id = ?', [id, companyId]);
}
static async countByEstado(companyId) {
const [rows] = await db.query(
'SELECT estado, COUNT(*) as count FROM herramientas WHERE company_id = ? GROUP BY estado',
[companyId]
);
const stats = { operativo: 0, en_mantenimiento: 0, danado: 0, baja: 0, total: 0 };
rows.forEach(r => {
if (stats[r.estado] !== undefined) {
stats[r.estado] = r.count;
}
stats.total += r.count;
});
return stats;
}
}
module.exports = Herramienta;
+104
View File
@@ -0,0 +1,104 @@
const db = require('../config/db');
class HerramientaAsignacion {
static async findAll(companyId, filtros = {}) {
let sql = `
SELECT tha.*,
u.nombre as tecnico_nombre,
u.email as tecnico_email,
hs.nombre as set_nombre,
hs.codigo as set_codigo,
h.nombre as herramienta_nombre,
h.codigo as herramienta_codigo,
tv.titulo as visita_titulo,
tv.ticket_id,
t.asunto as ticket_asunto,
adm.nombre as asignado_por_nombre
FROM tecnico_herramientas_asignaciones tha
INNER JOIN users u ON tha.tecnico_id = u.id
LEFT JOIN herramientas_sets hs ON tha.set_id = hs.id
LEFT JOIN herramientas h ON tha.herramienta_id = h.id
LEFT JOIN ticket_visitas tv ON tha.ticket_visita_id = tv.id
LEFT JOIN tickets t ON tv.ticket_id = t.id
LEFT JOIN users adm ON tha.asignado_por = adm.id
WHERE tha.company_id = ?
`;
const params = [companyId];
if (filtros.tecnico_id) {
sql += ' AND tha.tecnico_id = ?';
params.push(filtros.tecnico_id);
}
if (filtros.estado) {
sql += ' AND tha.estado = ?';
params.push(filtros.estado);
}
if (filtros.tipo_asignacion) {
sql += ' AND tha.tipo_asignacion = ?';
params.push(filtros.tipo_asignacion);
}
sql += ' ORDER BY tha.id DESC';
const [rows] = await db.query(sql, params);
return rows;
}
static async findByTecnico(companyId, tecnicoId) {
return await this.findAll(companyId, { tecnico_id: tecnicoId });
}
static async findById(id, companyId) {
const [rows] = await db.query(
`SELECT tha.*,
u.nombre as tecnico_nombre,
u.email as tecnico_email,
hs.nombre as set_nombre,
hs.codigo as set_codigo
FROM tecnico_herramientas_asignaciones tha
INNER JOIN users u ON tha.tecnico_id = u.id
LEFT JOIN herramientas_sets hs ON tha.set_id = hs.id
WHERE tha.id = ? AND tha.company_id = ?`,
[id, companyId]
);
return rows[0] || null;
}
static async create(data) {
const {
company_id,
tecnico_id,
set_id = null,
herramienta_id = null,
ticket_visita_id = null,
tipo_asignacion = 'dotacion_fija',
fecha_asignacion = new Date(),
observaciones_entrega = null,
asignado_por = null
} = data;
const [res] = await db.query(
`INSERT INTO tecnico_herramientas_asignaciones
(company_id, tecnico_id, set_id, herramienta_id, ticket_visita_id, tipo_asignacion, fecha_asignacion, estado, observaciones_entrega, asignado_por)
VALUES (?, ?, ?, ?, ?, ?, ?, 'asignado', ?, ?)`,
[company_id, tecnico_id, set_id, herramienta_id, ticket_visita_id, tipo_asignacion, fecha_asignacion, observaciones_entrega, asignado_por]
);
return res.insertId;
}
static async devolver(id, companyId, { observaciones_devolucion = null, estado = 'devuelto' }) {
await db.query(
`UPDATE tecnico_herramientas_asignaciones
SET estado = ?,
fecha_devolucion = NOW(),
observaciones_devolucion = ?
WHERE id = ? AND company_id = ?`,
[estado, observaciones_devolucion, id, companyId]
);
}
static async delete(id, companyId) {
await db.query('DELETE FROM tecnico_herramientas_asignaciones WHERE id = ? AND company_id = ?', [id, companyId]);
}
}
module.exports = HerramientaAsignacion;
+108
View File
@@ -0,0 +1,108 @@
const db = require('../config/db');
class HerramientaSet {
static async findAll(companyId) {
const [sets] = await db.query(
`SELECT hs.*,
COUNT(hsi.id) as total_herramientas,
COALESCE(SUM(hsi.cantidad), 0) as total_items
FROM herramientas_sets hs
LEFT JOIN herramientas_set_items hsi ON hs.id = hsi.set_id
WHERE hs.company_id = ?
GROUP BY hs.id
ORDER BY hs.id DESC`,
[companyId]
);
for (const s of sets) {
s.herramientas = await this.getItems(s.id);
}
return sets;
}
static async findById(id, companyId) {
const [rows] = await db.query(
'SELECT * FROM herramientas_sets WHERE id = ? AND company_id = ?',
[id, companyId]
);
if (!rows[0]) return null;
const set = rows[0];
set.herramientas = await this.getItems(set.id);
return set;
}
static async getItems(setId) {
const [items] = await db.query(
`SELECT h.*, hsi.cantidad
FROM herramientas_set_items hsi
INNER JOIN herramientas h ON hsi.herramienta_id = h.id
WHERE hsi.set_id = ?
ORDER BY h.nombre ASC`,
[setId]
);
return items;
}
static async create(data, itemIds = []) {
const {
company_id,
nombre,
codigo = null,
descripcion = null,
categoria = 'general',
is_active = 1
} = data;
const [res] = await db.query(
`INSERT INTO herramientas_sets (company_id, nombre, codigo, descripcion, categoria, is_active)
VALUES (?, ?, ?, ?, ?, ?)`,
[company_id, nombre, codigo, descripcion, categoria, is_active]
);
const setId = res.insertId;
if (Array.isArray(itemIds) && itemIds.length > 0) {
const values = itemIds.map(hId => [setId, parseInt(hId), 1]);
await db.query(
'INSERT INTO herramientas_set_items (set_id, herramienta_id, cantidad) VALUES ?',
[values]
);
}
return setId;
}
static async update(id, companyId, data, itemIds = null) {
const fields = [];
const values = [];
const allowed = ['nombre', 'codigo', 'descripcion', 'categoria', 'is_active'];
for (const [key, val] of Object.entries(data)) {
if (allowed.includes(key)) {
fields.push(`${key} = ?`);
values.push(val);
}
}
if (fields.length > 0) {
values.push(id, companyId);
await db.query(`UPDATE herramientas_sets SET ${fields.join(', ')} WHERE id = ? AND company_id = ?`, values);
}
if (Array.isArray(itemIds)) {
await db.query('DELETE FROM herramientas_set_items WHERE set_id = ?', [id]);
if (itemIds.length > 0) {
const valuesItems = itemIds.map(hId => [id, parseInt(hId), 1]);
await db.query(
'INSERT INTO herramientas_set_items (set_id, herramienta_id, cantidad) VALUES ?',
[valuesItems]
);
}
}
}
static async delete(id, companyId) {
await db.query('DELETE FROM herramientas_sets WHERE id = ? AND company_id = ?', [id, companyId]);
}
}
module.exports = HerramientaSet;
+8 -4
View File
@@ -17,13 +17,15 @@ class TicketVisita {
fecha_fin = null,
estado = 'programada',
checklist_json = null,
historial_json = null
historial_json = null,
set_herramientas_id = null,
set_herramientas_nombre = null
} = data;
const [result] = await db.query(
`INSERT INTO ticket_visitas
(company_id, ticket_id, external_id, tecnico_email, tecnico_nombre, titulo, descripcion, ubicacion, latitud, longitud, fecha_visita, fecha_fin, estado, checklist_json, historial_json)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
(company_id, ticket_id, external_id, tecnico_email, tecnico_nombre, titulo, descripcion, ubicacion, latitud, longitud, fecha_visita, fecha_fin, estado, checklist_json, historial_json, set_herramientas_id, set_herramientas_nombre)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
company_id,
ticket_id,
@@ -39,7 +41,9 @@ class TicketVisita {
fecha_fin,
estado,
typeof checklist_json === 'object' && checklist_json !== null ? JSON.stringify(checklist_json) : checklist_json,
typeof historial_json === 'object' && historial_json !== null ? JSON.stringify(historial_json) : historial_json
typeof historial_json === 'object' && historial_json !== null ? JSON.stringify(historial_json) : historial_json,
set_herramientas_id,
set_herramientas_nombre
]
);
return result.insertId;