import { BadRequestException, Injectable, NotFoundException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Equipo } from './entities/equipo.entity'; import { UpdateEquipoDto } from './dto/update-equipo.dto'; import axios from 'axios'; import e, { Response } from 'express'; import * as ExcelJS from 'exceljs'; import { Adscripcion, Estado, Laboratorio, Marca, Periferico, Procesador, Proyecto, SistemaOperativo, TipoEquipo, Uso, } from './entities/catalogo.entities'; import { CreateEquipoDto } from './dto/create-equipo.dto'; import { MovimientoService } from 'src/movimiento/movimiento.service'; @Injectable() export class EquipoService { constructor( private readonly movimiento: MovimientoService, @InjectRepository(Equipo) private readonly equipoRepository: Repository, @InjectRepository(Uso) private readonly usoRepo: Repository, @InjectRepository(Marca) private readonly marcaRepo: Repository, @InjectRepository(Estado) private readonly estadoRepo: Repository, @InjectRepository(Adscripcion) private readonly adscripcionRepo: Repository, @InjectRepository(TipoEquipo) private readonly tipoEquipoRepo: Repository, @InjectRepository(SistemaOperativo) private readonly sistemaOperativoRepo: Repository, @InjectRepository(Procesador) private readonly procesadorRepo: Repository, @InjectRepository(Periferico) private readonly perifericoRepo: Repository, @InjectRepository(Laboratorio) private readonly laboratorioRepo: Repository, @InjectRepository(Proyecto) private readonly proyectoRepo: Repository, ) {} async deleteEquipo(inventario: string): Promise<{ message: string }> { const equipo = await this.equipoRepository.findOne({ where: { inventario }, }); if (!equipo) { throw new NotFoundException( `No se encontró el equipo con id ${inventario}`, ); } await this.equipoRepository.remove(equipo); return { message: `Equipo con inv ${inventario} eliminado correctamente`, }; } async buscarEquipos(filtros: any, page: number = 1, limit: number = 10) { const query = this.equipoRepository .createQueryBuilder('equipo') .leftJoinAndSelect('equipo.marca', 'marca') .leftJoinAndSelect('equipo.estado', 'estado') .leftJoinAndSelect('equipo.adscripcion', 'adscripcion') .leftJoinAndSelect('equipo.tipoEquipo', 'tipoEquipo') .leftJoinAndSelect('equipo.sistemaOperativo', 'sistemaOperativo') .leftJoinAndSelect('equipo.procesador', 'procesador') .leftJoinAndSelect('equipo.tipoUso', 'tipoUso'); // Filtros dinámicos if (filtros.inventario) { query.andWhere('equipo.inventario LIKE :inventario', { inventario: `%${filtros.inventario}%`, }); } if (filtros.tipoEquipo) { query.andWhere('tipoEquipo.id = :tipoEquipo', { tipoEquipo: filtros.tipoEquipo, }); } if (filtros.antiguedad) { query.andWhere('equipo.antiguedad LIKE :antiguedad', { antiguedad: `%${filtros.antiguedad}%`, }); } if (filtros.marca) { query.andWhere('marca.id = :marca', { marca: filtros.marca }); } if (filtros.estado) { query.andWhere('estado.id = :estado', { estado: filtros.estado }); } if (filtros.adscripcion) { query.andWhere('adscripcion.id = :adscripcion', { adscripcion: filtros.adscripcion, }); } if (filtros.tipoUso) { query.andWhere('tipoUso.id = :tipoUso', { tipoUso: filtros.tipoUso }); } if (filtros.procesador) { query.andWhere('procesador.id = :procesador', { procesador: filtros.procesador, }); } if (filtros.sistemaOperativo) { query.andWhere('sistemaOperativo.id = :sistemaOperativo', { sistemaOperativo: filtros.sistemaOperativo, }); } // Paginación query.skip((page - 1) * limit).take(limit); // Ejecutar consulta const [data, total] = await query.getManyAndCount(); return { total, page, limit, data, }; } async updateEquipo(equipoNuevo: UpdateEquipoDto, id_equipo: number, id_user) { let equipo = await this.equipoRepository.findOne({ where: { id_equipo }, relations: ['tipoEquipo'], }); if (!equipo) { throw new NotFoundException('No se encontó equipo'); } const updateData = { id_equipo, lugar: equipoNuevo.lugar ? equipoNuevo.lugar : undefined, serie: equipoNuevo.serie ? equipoNuevo.serie : undefined, modelo: equipoNuevo.modelo ? equipoNuevo.modelo : undefined, estado: equipoNuevo.id_estado ? { id_estado: equipoNuevo.id_estado } : undefined, adscripcion: equipoNuevo.id_adscripcion ? { id_adscripcion: equipoNuevo.id_adscripcion } : undefined, sistemaOperativo: equipoNuevo.id_sistema_operativo ? { id_sistema_operativo: equipoNuevo.id_sistema_operativo } : undefined, procesador: equipoNuevo.id_procesador ? { id_procesador: equipoNuevo.id_procesador } : undefined, tipoUso: equipoNuevo.id_tipo_uso ? { id_uso: equipoNuevo.id_tipo_uso } : undefined, marca: equipoNuevo.id_marca ? { id_marca: equipoNuevo.id_marca } : undefined, periferico: equipoNuevo.id_periferico ? { id_periferico: equipoNuevo.id_periferico } : undefined, laboratorio: equipoNuevo.id_laboratorio ? { id_laboratorio: equipoNuevo.id_laboratorio } : undefined, proyecto: equipoNuevo.id_proyecto ? { id_proyecto: equipoNuevo.id_proyecto } : undefined, }; console.log(updateData); const equipoUpdate = await this.equipoRepository.preload(updateData); console.log(equipoNuevo); if (!equipoUpdate) { throw new Error(`No se encontró el equipo con id ${id_equipo}`); } const mov = await this.movimiento.crear({ idUsuario: id_user, idEquipo: id_equipo, observaciones: equipoNuevo.observaciones, }); return await this.equipoRepository.save(equipoUpdate); } async findEquipo(inventario: string) { const equipo = await this.equipoRepository.findOne({ where: { inventario }, relations: [ 'marca', 'estado', 'adscripcion', 'tipoEquipo', 'sistemaOperativo', 'procesador', 'tipoUso', 'periferico', 'proyecto', 'laboratorio', ], }); if (!equipo) { throw new NotFoundException( `No se encontró el equipo con inventario ${inventario}`, ); } const responsable = await this.searchResponsable( equipo.adscripcion.adscripcion, ); const observaciones = await this.movimiento.buscar(equipo.id_equipo); return { ...equipo, ...responsable, ...observaciones }; } async searchResponsable(nombreAdscripcion: string) { const adscripcion = await this.adscripcionRepo.findOneBy({ adscripcion: nombreAdscripcion, }); if (!adscripcion) { throw new Error( `No se encontró la adscripción con nombre ${nombreAdscripcion}`, ); } let entrada = { tipoBusqueda: 'area', IdUnidadResponsable: adscripcion.id_adscripcion, }; const url = process.env.API_FUNCIONARIOS; if (!url) throw new Error('Url no definida en las variables de entorno'); console.log(entrada); const response = await axios.post(url, entrada); return response.data; } async create(equipo: CreateEquipoDto, id_usuario: number) { const equipoExistente = await this.equipoRepository.findOne({ where: { inventario: equipo.inventario }, }); if (equipoExistente) { throw new BadRequestException('El equipo ya existe'); } const datosBase = { inventario: equipo.inventario, serie: equipo.serie, lugar: equipo.lugar, fechaFactura: equipo.fechaFactura, antiguedad: equipo.antiguedad, modelo: equipo.modelo, estado: { id_estado: equipo.id_estado }, adscripcion: { id_adscripcion: equipo.id_adscripcion }, tipoUso: { id_uso: equipo.id_uso }, marca: { id_marca: equipo.id_marca }, tipoEquipo: { id_tipo_de_equipo: equipo.id_tipo_equipo }, proyecto: { id_proyecto: equipo.id_proyecto }, laboratorio: { id_laboratorio: equipo.id_laboratorio }, }; const nuevoEquipo = this.equipoRepository.create( equipo.isImpresora ? { ...datosBase, periferico: { id_periferico: equipo.id_periferico } } : { ...datosBase, sistemaOperativo: { id_sistema_operativo: equipo.id_sistema_operativo, }, procesador: { id_procesador: equipo.id_procesador }, }, ); let equip = await this.equipoRepository.save(nuevoEquipo); if (!equip) throw new Error('No se creó el equipo'); // Crear movimiento asociado await this.movimiento.crear({ idEquipo: equip.id_equipo, idUsuario: id_usuario, }); return equip; } async findAllUsos() { return await this.usoRepo.find(); } async findAllMarcas() { return await this.marcaRepo.find(); } async findAllEstados() { return await this.estadoRepo.find(); } async findAllProyectos() { return await this.proyectoRepo.find(); } async findAllLaboratorios() { return await this.laboratorioRepo.find(); } async findAllAdscripciones() { return await this.adscripcionRepo.find(); } async findAllTiposEquipo() { return await this.tipoEquipoRepo.find(); } async findAllSistemasOperativos() { return await this.sistemaOperativoRepo.find(); } async findAllProcesadores() { return await this.procesadorRepo.find(); } async findAllPerifericos() { return await this.perifericoRepo.find(); } async findAllProcesadorTipoEquipos() { return await this.procesadorRepo.find(); } async createEcxel(res: Response) { const data = await this.generar_reporte(); return this.exportarReporteAExcel(data, res); } async exportarReporteAExcel(data: any, res: Response) { const workbook = new ExcelJS.Workbook(); for (const key of Object.keys(data)) { const sheetName = key.substring(0, 30); // Excel limita a 30 caracteres const sheet = workbook.addWorksheet(sheetName); const rows = data[key]; if (!Array.isArray(rows) || rows.length === 0) { sheet.addRow([`(sin datos)`]); continue; } // Obtener columnas automáticamente const headers = Object.keys(rows[0]); // Crear encabezado sheet.addRow(headers); // Agregar filas for (const row of rows) { sheet.addRow(headers.map((h) => row[h])); } // Estilo básico sheet.getRow(1).font = { bold: true }; sheet.getRow(1).alignment = { horizontal: 'center' }; sheet.columns.forEach((col) => { col.width = 35; }); } // Enviar el archivo al cliente res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ); res.setHeader('Content-Disposition', 'attachment; filename=reporte.xlsx'); await workbook.xlsx.write(res); res.end(); } async createReport() { let obj = await this.generar_reporte(); const objectParse = Array.isArray(obj) ? obj.map((item) => this.flattenObject(item)) : [this.flattenObject(obj)]; return objectParse; } async contar_tipoEquipos_tipoUso(): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') // une la tabla usuario .innerJoin('e.tipoUso', 'u') // une la tabla categoria .select(['u.tipo_uso AS uso', 't.tipo_equipo AS categoria']) .addSelect('COUNT(*) AS total') .groupBy('t.id_tipo_de_equipo') .addGroupBy('u.id_uso') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_tipoEquipos_sistemasOperativos( tipo_equipo: string[], ): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .innerJoin('e.sistemaOperativo', 's') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo }) .select(['s.sistema_operativo AS sistema_operativo']) .addSelect('COUNT(*) AS total') .groupBy('s.sistema_operativo') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_tipoEquipos_procesador(tipo_equipo: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.procesador', 'p') .innerJoin('e.tipoEquipo', 't') .innerJoin('e.tipoUso', 'u') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo }) .select(['p.procesador AS procesador', 'u.tipo_uso AS uso']) .addSelect('COUNT(*) AS total') .groupBy('p.procesador') .addGroupBy('u.tipo_uso') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_tipoEquipos(tipo_equipo: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo }) .select('COUNT(*) AS total') .getRawOne(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_tipoEquipos_antiguedad(): Promise { const totalEscrtitorio = await this.contar_tipoEquipos(escritorio); const totalPortatiles = await this.contar_tipoEquipos(portatil); const totalAltoRendimiento = await this.contar_tipoEquipos(['SERVIDOR']); try { const countEscritorios = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo: escritorio, }) .select(['e.antiguedad AS antiguedad']) .addSelect('COUNT(*) AS total') .groupBy('e.antiguedad') .getRawMany(); const countPortatiles = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo: portatil, }) .select(['e.antiguedad AS antiguedad']) .addSelect('COUNT(*) AS total') .groupBy('e.antiguedad') .getRawMany(); const countAltoRendimiento = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .where('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo: ['SERVIDOR'], }) .select(['e.antiguedad AS antiguedad']) .addSelect('COUNT(*) AS total') .groupBy('e.antiguedad') .getRawMany(); return { escritorios: countEscritorios.map((i) => ({ antiguedad: i.antiguedad, total: i.total, porcentaje: ((i.total / totalEscrtitorio.total) * 100).toFixed(2), })), portatiles: countPortatiles.map((i) => ({ antiguedad: i.antiguedad, total: i.total, porcentaje: ((i.total / totalPortatiles.total) * 100).toFixed(2), })), altoRendimiento: countAltoRendimiento.map((i) => ({ antiguedad: i.antiguedad, total: i.total, porcentaje: ((i.total / totalAltoRendimiento.total) * 100).toFixed(2), })), }; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async garantia(tipo_equipo: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoEquipo', 't') .where('e.fechaFactura >= DATE_SUB(CURDATE(), INTERVAL 3 YEAR)') .andWhere('t.tipo_equipo IN (:...tipo_equipo)', { tipo_equipo }) .getCount(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async equipos_impesion(perifericos: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.periferico', 'p') .select(['p.periferico AS periferico']) .where('p.periferico IN (:...perifericos)', { perifericos }) .getCount(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async equipos_impesion_group(perifericos: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.periferico', 'p') .where('p.periferico IN (:...perifericos)', { perifericos }) .select('p.periferico AS periferico') .addSelect('COUNT(*) AS total') .groupBy('p.periferico') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_periferico_tipoUso(perifericos: string[]): Promise { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.tipoUso', 't') .innerJoin('e.periferico', 'p') .where('p.periferico IN (:...perifericos)', { perifericos }) .select(['t.tipo_uso AS uso']) .addSelect('COUNT(*) AS total') .groupBy('t.tipo_uso') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos por tipo y uso:', error); throw error; } } async contar_perifericos_antiguedad( impresion: string[], digitalizacion: string[], ): Promise<{ impresion: { antiguedad: any; total: number; porcentaje: string }[]; digitalizacion: { antiguedad: any; total: number; porcentaje: string }[]; }> { try { // Helper para obtener total por tipos de equipo (count simple, sin group by) const getTotalPorTipos = async (tipos: string[]): Promise => { if (!tipos || tipos.length === 0) return 0; return this.equipoRepository .createQueryBuilder('e') .innerJoin('e.periferico', 'p') .where('p.periferico IN (:...tipos)', { tipos }) .select('COUNT(*) AS total') .getRawOne(); }; // Ejecutar ambos totales en paralelo const [totalI, totalD] = await Promise.all([ getTotalPorTipos(impresion), getTotalPorTipos(digitalizacion), ]); const totalImpresion = totalI.total; const totalDigitalizacion = totalD.total; // Obtener conteos por antiguedad para impresion const countImpresionPromise = this.equipoRepository .createQueryBuilder('e') .innerJoin('e.periferico', 'p') .innerJoin('e.tipoEquipo', 't') .where('p.periferico IN (:...periferico)', { periferico: impresion }) .select(['e.antiguedad AS antiguedad']) .addSelect('COUNT(e.id_equipo) AS total') .groupBy('e.antiguedad') .getRawMany(); // Obtener conteos por antiguedad para digitalizacion const countDigitalizacionPromise = this.equipoRepository .createQueryBuilder('e') .innerJoin('e.periferico', 'p') .innerJoin('e.tipoEquipo', 't') .where('p.periferico IN (:...periferico)', { periferico: digitalizacion, }) .select(['e.antiguedad AS antiguedad']) .addSelect('COUNT(e.id_equipo) AS total') .groupBy('e.antiguedad') .getRawMany(); // Ejecutar ambas consultas en paralelo const [countImpresion, countDigitalizacion] = await Promise.all([ countImpresionPromise, countDigitalizacionPromise, ]); console.log('I', totalImpresion); console.log('D', totalDigitalizacion); console.log('I', countImpresion); console.log('D', countDigitalizacion); // Tipo para salida interface Porcentaje { antiguedad: any; total: number; porcentaje: string; } // Mapear resultados calculando porcentajes (evitar async dentro de map/forEach) const porcentajesImpresion: Porcentaje[] = countImpresion.map((item) => { const total = Number(item.total) || 0; const porcentaje = totalImpresion > 0 ? ((total / totalImpresion) * 100).toFixed(2) : '0.00'; return { antiguedad: item.antiguedad, total, porcentaje, }; }); const porcentajesDigitalizacion: Porcentaje[] = countDigitalizacion.map( (item) => { const total = Number(item.total) || 0; const porcentaje = totalDigitalizacion > 0 ? ((total / totalDigitalizacion) * 100).toFixed(2) : '0.00'; return { antiguedad: item.antiguedad, total, porcentaje, }; }, ); return { impresion: porcentajesImpresion, digitalizacion: porcentajesDigitalizacion, }; } catch (error) { console.error('Error en contar_perifericos_antiguedad:', error); throw error; } } async proyecto_equipos() { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.proyecto', 'p') .select(['p.proyecto AS proyecto']) .addSelect('COUNT(*) AS total') .groupBy('p.proyecto') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos proyectos:', error); throw error; } } async laboratorio_equipos() { try { const count = await this.equipoRepository .createQueryBuilder('e') .innerJoin('e.laboratorio', 'l') .select(['l.laboratorio AS laboratorio']) .addSelect('COUNT(*) AS total') .groupBy('l.laboratorio') .getRawMany(); return count; } catch (error) { console.error('Error al contar equipos proyectos:', error); throw error; } } async generar_reporte() { const [ contar_equipos_de_tipo_uso, sisitema_operativo_pc, sisitema_operativo_mac, sisitema_operativo_portatiles_pc, sisitema_operativo_portatiles_mac, sisitema_operativo_servidores, contar_pc_procesador, contar_mac_procesador, contar_portatil_procesador, contar_portatil_mac_procesador, contar_servidor_procesador, porcentaje_antiguedad, garantia_escritorio, garantia_portatiles, garantia_servidor, contar_impresoras, contar_impresoras_tipo_uso, contar_digitalizacion, contar_contar_digitalizacion_u, contar_perifericos_antiguedad, cotar_laboratorios, contar_proyectos, ] = await Promise.all([ this.contar_tipoEquipos_tipoUso(), this.contar_tipoEquipos_sistemasOperativos(equpos_pc), this.contar_tipoEquipos_sistemasOperativos([estritorio_mac]), this.contar_tipoEquipos_sistemasOperativos(portatiles), this.contar_tipoEquipos_sistemasOperativos([portatiles_mac]), this.contar_tipoEquipos_sistemasOperativos([servidor]), this.contar_tipoEquipos_procesador(equpos_pc), this.contar_tipoEquipos_procesador([estritorio_mac]), this.contar_tipoEquipos_procesador(portatiles), this.contar_tipoEquipos_procesador([portatiles_mac]), this.contar_tipoEquipos_procesador([servidor]), this.contar_tipoEquipos_antiguedad(), this.garantia(escritorio), this.garantia(portatil), this.garantia([servidor]), this.equipos_impesion_group(impresoras), this.contar_periferico_tipoUso(impresoras), this.equipos_impesion_group(dijitales), this.contar_periferico_tipoUso(dijitales), this.contar_perifericos_antiguedad(impresoras, dijitales), this.proyecto_equipos(), this.laboratorio_equipos(), ]); return { contar_equipos_de_tipo_uso, sisitema_operativo_pc, sisitema_operativo_mac, sisitema_operativo_portatiles_pc, sisitema_operativo_portatiles_mac, sisitema_operativo_servidores, contar_pc_procesador, contar_mac_procesador, contar_portatil_procesador, contar_portatil_mac_procesador, contar_servidor_procesador, porcentaje_antiguedad, garantia_escritorio, garantia_portatiles, garantia_servidor, contar_impresoras, contar_impresoras_tipo_uso, contar_digitalizacion, contar_contar_digitalizacion_u, contar_perifericos_antiguedad, cotar_laboratorios, contar_proyectos, }; } async exportToClient(data: any, res: Response) { // Aseguramos que los datos sean un arreglo de objetos planos const rows = Array.isArray(data) ? data.map((item) => this.flattenObject(item)) : [this.flattenObject(data)]; // Crear workbook y hoja const workbook = new ExcelJS.Workbook(); const worksheet = workbook.addWorksheet('Datos'); // Definir encabezados dinámicamente const headers = Object.keys(rows[0] || {}); worksheet.columns = headers.map((h) => ({ header: h, key: h, width: 25, })); // Agregar filas worksheet.addRows(rows); // Estilizar encabezados const headerRow = worksheet.getRow(1); headerRow.font = { bold: true }; headerRow.alignment = { vertical: 'middle', horizontal: 'center' }; headerRow.eachCell((cell) => { cell.border = { top: { style: 'thin' }, left: { style: 'thin' }, bottom: { style: 'thin' }, right: { style: 'thin' }, }; }); // Configurar cabeceras HTTP res.setHeader( 'Content-Type', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ); res.setHeader('Content-Disposition', 'attachment; filename="reporte.xlsx"'); // Enviar directamente al cliente await workbook.xlsx.write(res); res.end(); } private flattenObject(obj: any, parentKey = '', res: any = {}): any { for (const key of Object.keys(obj || {})) { const propName = parentKey ? `${parentKey}.${key}` : key; const value = obj[key]; if (value && typeof value === 'object' && !Array.isArray(value)) { this.flattenObject(value, propName, res); } else if (Array.isArray(value)) { res[propName] = JSON.stringify(value); } else { res[propName] = value; } } return res; } } let escritorio = ['ESCRITORIO PC', 'ESCRITORIO MAC OS ', 'ESCRITORIO LINUX']; let portatil = [ 'PORTÁTILES WINDOWS', 'PORTÁTILES CHROMEBOOK', 'PORTÁTILES MAC OS', ]; let equipos_mac = ['ESCRITORIO MAC OS ', 'PORTÁTILES MAC OS']; let equpos_pc = ['ESCRITORIO PC', 'ESCRITORIO LINUX']; let estritorio_mac = 'ESCRITORIO MAC OS '; let portatiles_mac = 'PORTÁTILES MAC OS'; let portatiles = ['PORTÁTILES WINDOWS', 'PORTÁTILES CHROMEBOOK']; let tabletas = ['TABLETA ANDROID', 'TABLETA iPAD OS']; let impresoras = [ 'INYECCIÓN TINTA', 'LÁSER DE ALTO VOLUMEN B/N', 'LÁSER DE ALTO VOLUMEN COLOR', 'LÁSER PEQUEÑA B/N', 'LÁSER PEQUEÑA COLOR', 'MATRIZ DE PUNTOS', 'MULTIFUNCIONALES', 'IMPRESORA TÉRMICA', 'IMPRESORA CREDENCIALES', 'PLOTTER', ]; let dijitales = [ '3D', 'DIGITALIZADOR DE CAMA PLANA PARA OFICINA', 'DIGITALIZADOR DE GRAN VOLUMEN', 'DIGITALIZADOR CON ALIMENTADOR DE HOJAS PARA OFICINA', ]; let servidor = 'SERVIDOR';