Files
MSP-CAS/src/server/trpc/routers/celulares.router.ts

384 lines
11 KiB
TypeScript

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 }
}),
})