Fase 1 - UI: dark mode, skeletons, toasts Fase 2 - Filtros: avanzados Mi día, búsqueda global Ctrl+K Fase 3 - Datos: export Excel, notas apartados, auditoría Fase 4 - Dashboard: gráficas vendedor + métricas uso Fase 5 - Responsive: tablets optimizadas Fase 6 - PWA: IndexedDB offline, push notifications Backend: 3 tablas nuevas, 6 services, 6 endpoints nuevos 721 API tests + fronts verdes.
88 lines
2.6 KiB
TypeScript
88 lines
2.6 KiB
TypeScript
import webPush from 'web-push'
|
|
import { config } from '../config/index.js'
|
|
import { prisma } from './prisma.js'
|
|
|
|
// Configurar VAPID solo si las claves están presentes
|
|
if (config.push.vapidPublicKey && config.push.vapidPrivateKey) {
|
|
webPush.setVapidDetails(
|
|
config.push.vapidEmail,
|
|
config.push.vapidPublicKey,
|
|
config.push.vapidPrivateKey,
|
|
)
|
|
}
|
|
|
|
export interface PushSubscriptionInput {
|
|
endpoint: string
|
|
p256dh: string
|
|
auth: string
|
|
}
|
|
|
|
/** Registra o actualiza la suscripción push de un usuario */
|
|
export async function registrarSuscripcion(usuarioId: string, subscription: PushSubscriptionInput): Promise<void> {
|
|
await prisma.pushSubscription.upsert({
|
|
where: { usuarioId },
|
|
update: {
|
|
endpoint: subscription.endpoint,
|
|
p256dh: subscription.p256dh,
|
|
auth: subscription.auth,
|
|
},
|
|
create: {
|
|
usuarioId,
|
|
endpoint: subscription.endpoint,
|
|
p256dh: subscription.p256dh,
|
|
auth: subscription.auth,
|
|
},
|
|
})
|
|
}
|
|
|
|
/** Elimina la suscripción push de un usuario */
|
|
export async function eliminarSuscripcion(usuarioId: string): Promise<void> {
|
|
await prisma.pushSubscription.deleteMany({ where: { usuarioId } })
|
|
}
|
|
|
|
/** Devuelve la VAPID public key para el frontend */
|
|
export function obtenerVapidPublicKey(): string {
|
|
return config.push.vapidPublicKey
|
|
}
|
|
|
|
/** Busca el usuarioId local a partir de un mondayUserId */
|
|
export async function usuarioIdPorMondayUserId(mondayUserId: number): Promise<string | null> {
|
|
const usuario = await prisma.usuario.findFirst({
|
|
where: { mondayUserId },
|
|
select: { id: true },
|
|
})
|
|
return usuario?.id ?? null
|
|
}
|
|
|
|
/** Envía una notificación push a un usuario. Silencioso si no tiene suscripción o si web-push no está configurado. */
|
|
export async function enviarPush(
|
|
usuarioId: string,
|
|
titulo: string,
|
|
cuerpo: string,
|
|
url?: string,
|
|
): Promise<boolean> {
|
|
if (!config.push.vapidPublicKey || !config.push.vapidPrivateKey) return false
|
|
|
|
const sub = await prisma.pushSubscription.findUnique({ where: { usuarioId } })
|
|
if (!sub) return false
|
|
|
|
const payload = JSON.stringify({ titulo, cuerpo, url: url ?? '/' })
|
|
|
|
try {
|
|
await webPush.sendNotification(
|
|
{ endpoint: sub.endpoint, keys: { p256dh: sub.p256dh, auth: sub.auth } },
|
|
payload,
|
|
)
|
|
return true
|
|
} catch (err: unknown) {
|
|
// Si la suscripción ya no es válida (404 o 410), eliminarla
|
|
const status = (err as { statusCode?: number }).statusCode
|
|
if (status === 404 || status === 410) {
|
|
await prisma.pushSubscription.deleteMany({ where: { usuarioId } })
|
|
}
|
|
// eslint-disable-next-line no-console
|
|
console.error(`[push] No se pudo enviar a ${usuarioId}:`, err)
|
|
return false
|
|
}
|
|
}
|