import { z } from 'zod' import { TRPCError } from '@trpc/server' import { TipoDispositivo } from '@prisma/client' import { router, protectedProcedure, adminProcedure } from '../trpc' import { HeadwindClient } from '@/server/services/headwind/client' export const celularesRouter = router({ // Listar celulares y tablets list: protectedProcedure .input( z.object({ clienteId: z.string().optional(), estado: z.enum(['ONLINE', 'OFFLINE', 'ALERTA', 'MANTENIMIENTO', 'DESCONOCIDO']).optional(), search: z.string().optional(), page: z.number().default(1), limit: z.number().default(20), }).optional() ) .query(async ({ ctx, input }) => { const { clienteId, estado, search, page = 1, limit = 20 } = input || {} const where = { tipo: { in: ['CELULAR', 'TABLET'] as TipoDispositivo[] }, ...(ctx.user.clienteId ? { clienteId: ctx.user.clienteId } : {}), ...(clienteId ? { clienteId } : {}), ...(estado ? { estado } : {}), ...(search ? { OR: [ { nombre: { contains: search, mode: 'insensitive' as const } }, { imei: { contains: search } }, { numeroTelefono: { contains: search } }, ], } : {}), } const [dispositivos, total] = await Promise.all([ ctx.prisma.dispositivo.findMany({ where, include: { cliente: { select: { id: true, nombre: true, codigo: true } }, ubicacion: { select: { id: true, nombre: true } }, }, orderBy: [{ estado: 'asc' }, { nombre: 'asc' }], skip: (page - 1) * limit, take: limit, }), ctx.prisma.dispositivo.count({ where }), ]) return { dispositivos, pagination: { total, page, limit, totalPages: Math.ceil(total / limit), }, } }), // Obtener celular por ID byId: protectedProcedure .input(z.object({ id: z.string() })) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.id }, include: { cliente: true, ubicacion: true, alertas: { where: { estado: 'ACTIVA' }, orderBy: { createdAt: 'desc' }, take: 10, }, }, }) if (!dispositivo) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Dispositivo no encontrado' }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } return dispositivo }), // Obtener ubicacion actual ubicacion: protectedProcedure .input(z.object({ dispositivoId: z.string() })) .query(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, select: { id: true, nombre: true, latitud: true, longitud: true, gpsUpdatedAt: true, clienteId: true, }, }) if (!dispositivo) { throw new TRPCError({ code: 'NOT_FOUND', message: 'Dispositivo no encontrado' }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } return { lat: dispositivo.latitud, lng: dispositivo.longitud, updatedAt: dispositivo.gpsUpdatedAt, } }), // Solicitar actualizacion de ubicacion solicitarUbicacion: protectedProcedure .input(z.object({ dispositivoId: z.string() })) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.requestLocation(dispositivo.headwindId) return { success: true, message: 'Solicitud de ubicacion enviada' } }), // Bloquear dispositivo bloquear: protectedProcedure .input( z.object({ dispositivoId: z.string(), mensaje: z.string().optional(), }) ) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.lockDevice(dispositivo.headwindId, input.mensaje) await ctx.prisma.auditLog.create({ data: { usuarioId: ctx.user.id, dispositivoId: input.dispositivoId, accion: 'bloquear', recurso: 'celular', detalles: { mensaje: input.mensaje }, }, }) return { success: true } }), // Desbloquear dispositivo desbloquear: protectedProcedure .input(z.object({ dispositivoId: z.string() })) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.unlockDevice(dispositivo.headwindId) await ctx.prisma.auditLog.create({ data: { usuarioId: ctx.user.id, dispositivoId: input.dispositivoId, accion: 'desbloquear', recurso: 'celular', }, }) return { success: true } }), // Hacer sonar dispositivo sonar: protectedProcedure .input(z.object({ dispositivoId: z.string() })) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.ringDevice(dispositivo.headwindId) return { success: true } }), // Enviar mensaje al dispositivo enviarMensaje: protectedProcedure .input( z.object({ dispositivoId: z.string(), mensaje: z.string().min(1), }) ) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.sendMessage(dispositivo.headwindId, input.mensaje) return { success: true } }), // Borrar datos (factory reset) borrarDatos: adminProcedure .input(z.object({ dispositivoId: z.string() })) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } const headwindClient = new HeadwindClient() await headwindClient.wipeDevice(dispositivo.headwindId) await ctx.prisma.auditLog.create({ data: { usuarioId: ctx.user.id, dispositivoId: input.dispositivoId, accion: 'borrar_datos', recurso: 'celular', }, }) return { success: true } }), // Instalar aplicacion instalarApp: protectedProcedure .input( z.object({ dispositivoId: z.string(), packageName: z.string(), }) ) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.installApp(dispositivo.headwindId, input.packageName) await ctx.prisma.auditLog.create({ data: { usuarioId: ctx.user.id, dispositivoId: input.dispositivoId, accion: 'instalar_app', recurso: 'celular', detalles: { packageName: input.packageName }, }, }) return { success: true } }), // Desinstalar aplicacion desinstalarApp: protectedProcedure .input( z.object({ dispositivoId: z.string(), packageName: z.string(), }) ) .mutation(async ({ ctx, input }) => { const dispositivo = await ctx.prisma.dispositivo.findUnique({ where: { id: input.dispositivoId }, }) if (!dispositivo || !dispositivo.headwindId) { throw new TRPCError({ code: 'BAD_REQUEST', message: 'Dispositivo no tiene Headwind MDM', }) } if (ctx.user.clienteId && ctx.user.clienteId !== dispositivo.clienteId) { throw new TRPCError({ code: 'FORBIDDEN', message: 'Acceso denegado' }) } const headwindClient = new HeadwindClient() await headwindClient.removeApp(dispositivo.headwindId, input.packageName) await ctx.prisma.auditLog.create({ data: { usuarioId: ctx.user.id, dispositivoId: input.dispositivoId, accion: 'desinstalar_app', recurso: 'celular', detalles: { packageName: input.packageName }, }, }) return { success: true } }), })