feat(api): webhooks Monday en tiempo real + alertas de inventario cableadas
- Webhook Oportunidades: cambio de etapa dispara escaneo (Aparto→Apartado, Ganado→Venta) además del round-robin de leads por create_item - Acepta los nombres reales de los eventos de Monday: create_pulse/pulseId y update_column_value (verificados en vivo) - Token interno (X-Internal-Token) para que los syncs locales llamen POST /cotizaciones/alertas-inventario sin JWT de usuario - sync-estatus-maestro.py notifica los cambios aplicados al endpoint de alertas (actividad al vendedor con cotizaciones recientes) - Fix P1008 SQLite: escaneos del poll cron secuenciales + connection_limit=1 en DATABASE_URL - Webhooks registrados en Monday sobre https://frasa.consultoria-as.com (Leads create_item, Oportunidades change_column_value); crons quedan como red de seguridad - Docs: sección 0 en AUTOMATIZACIONES-MONDAY.md con la matriz de eventos y los quirks de Monday
This commit is contained in:
@@ -12,6 +12,7 @@ vi.mock('../config/index.js', () => ({
|
||||
jwtExpiresIn: '7d',
|
||||
adminPassword: 'Frasa2026',
|
||||
},
|
||||
internalSyncToken: 'test-internal-token',
|
||||
},
|
||||
}))
|
||||
|
||||
@@ -129,4 +130,42 @@ describe('authMiddleware (AUTH_ENABLED=true)', () => {
|
||||
authMiddleware(reqFake({ method: 'GET', path: '/webhooks/monday' }), res, next)
|
||||
expect(status).toHaveBeenCalledWith(401)
|
||||
})
|
||||
|
||||
it('el token interno abre solo POST /cotizaciones/alertas-inventario', () => {
|
||||
authMiddleware(
|
||||
reqFake({
|
||||
method: 'POST',
|
||||
path: '/cotizaciones/alertas-inventario',
|
||||
headers: { 'x-internal-token': 'test-internal-token' },
|
||||
}),
|
||||
resFake().res,
|
||||
next,
|
||||
)
|
||||
expect(next).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('token interno incorrecto o en otra ruta → 401', () => {
|
||||
const casos: Partial<Request>[] = [
|
||||
// token incorrecto en el endpoint correcto
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/cotizaciones/alertas-inventario',
|
||||
headers: { 'x-internal-token': 'otro-token' },
|
||||
},
|
||||
// token correcto en otra ruta
|
||||
{ method: 'POST', path: '/clientes', headers: { 'x-internal-token': 'test-internal-token' } },
|
||||
{ method: 'GET', path: '/unidades', headers: { 'x-internal-token': 'test-internal-token' } },
|
||||
// otro método sobre el endpoint
|
||||
{
|
||||
method: 'GET',
|
||||
path: '/cotizaciones/alertas-inventario',
|
||||
headers: { 'x-internal-token': 'test-internal-token' },
|
||||
},
|
||||
]
|
||||
for (const c of casos) {
|
||||
const { res, status } = resFake()
|
||||
authMiddleware(reqFake(c), res, next)
|
||||
expect(status).toHaveBeenCalledWith(401)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user