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.
42 lines
1.3 KiB
TypeScript
42 lines
1.3 KiB
TypeScript
import type { Request, Response } from 'express'
|
|
import { z } from 'zod'
|
|
import {
|
|
registrarSuscripcion,
|
|
eliminarSuscripcion,
|
|
obtenerVapidPublicKey,
|
|
} from '../services/push.service.js'
|
|
|
|
const suscripcionSchema = z.object({
|
|
endpoint: z.string().url(),
|
|
p256dh: z.string().min(1),
|
|
auth: z.string().min(1),
|
|
})
|
|
|
|
/** POST /api/push/suscribir — registrar suscripción push del navegador */
|
|
export async function suscribirEndpoint(req: Request, res: Response): Promise<void> {
|
|
const usuarioId = req.usuarioToken?.sub
|
|
if (!usuarioId) {
|
|
res.status(401).json({ error: 'Token requerido' })
|
|
return
|
|
}
|
|
const body = suscripcionSchema.parse(req.body)
|
|
await registrarSuscripcion(usuarioId, body)
|
|
res.json({ ok: true })
|
|
}
|
|
|
|
/** POST /api/push/desuscribir — eliminar suscripción push */
|
|
export async function desuscribirEndpoint(req: Request, res: Response): Promise<void> {
|
|
const usuarioId = req.usuarioToken?.sub
|
|
if (!usuarioId) {
|
|
res.status(401).json({ error: 'Token requerido' })
|
|
return
|
|
}
|
|
await eliminarSuscripcion(usuarioId)
|
|
res.json({ ok: true })
|
|
}
|
|
|
|
/** GET /api/push/vapid-key — devuelve la VAPID public key (pública) */
|
|
export function vapidKeyEndpoint(_req: Request, res: Response): void {
|
|
res.json({ publicKey: obtenerVapidPublicKey() })
|
|
}
|