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:
@@ -6,36 +6,40 @@ import { recordatoriosCobranzaService } from './services/recordatorios-cobranza.
|
||||
|
||||
const app = crearApp()
|
||||
|
||||
// Crons de respaldo (el webhook público aún no existe: no hay SSL ni DNS).
|
||||
// Se desactivan con MONDAY_AUTOMATIONS_ENABLED=false.
|
||||
// Crons de respaldo: los webhooks de Monday (registrados sobre
|
||||
// https://frasa.consultoria-as.com/api/webhooks/monday) disparan estas mismas
|
||||
// reglas en tiempo real; estos barridos quedan como red de seguridad por si un
|
||||
// evento se pierde. Se desactivan con MONDAY_AUTOMATIONS_ENABLED=false.
|
||||
if (config.automatizaciones.enabled) {
|
||||
// Polling: asigna vendedor (round-robin) a leads nuevos sin persona asignada,
|
||||
// escala leads sin contactar y crea Apartados/Ventas al moverse Oportunidades.
|
||||
// Cada regla tiene su propio catch: un fallo de Monday no rompe las demás.
|
||||
// SECUENCIAL a propósito: SQLite serializa las escrituras y lanzar los tres
|
||||
// escaneos en paralelo provoca timeouts P1008 en AutomatizacionState.
|
||||
cron.schedule(config.automatizaciones.pollCron, () => {
|
||||
automatizacionesService
|
||||
.runRoundRobin()
|
||||
.then((r) => {
|
||||
void (async () => {
|
||||
try {
|
||||
const r = await automatizacionesService.runRoundRobin()
|
||||
if (r.asignados > 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[cron poll] round-robin: ${r.asignados} lead(s) asignado(s)`)
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => console.error('[cron poll] error en round-robin:', err))
|
||||
} catch (err: unknown) {
|
||||
console.error('[cron poll] error en round-robin:', err)
|
||||
}
|
||||
|
||||
automatizacionesService
|
||||
.escaneoEscalado()
|
||||
.then((r) => {
|
||||
try {
|
||||
const r = await automatizacionesService.escaneoEscalado()
|
||||
if (r.escalados > 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[cron poll] escalado: ${r.escalados} lead(s) escalado(s)`)
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => console.error('[cron poll] error en escalado:', err))
|
||||
} catch (err: unknown) {
|
||||
console.error('[cron poll] error en escalado:', err)
|
||||
}
|
||||
|
||||
automatizacionesService
|
||||
.escaneoOportunidades()
|
||||
.then((r) => {
|
||||
try {
|
||||
const r = await automatizacionesService.escaneoOportunidades()
|
||||
if (r.apartadosCreados.length > 0 || r.ventasCreadas.length > 0 || r.errores.length > 0) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
@@ -43,8 +47,10 @@ if (config.automatizaciones.enabled) {
|
||||
`${r.ventasCreadas.length} venta(s), ${r.errores.length} error(es)`,
|
||||
)
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => console.error('[cron poll] error en oportunidades:', err))
|
||||
} catch (err: unknown) {
|
||||
console.error('[cron poll] error en oportunidades:', err)
|
||||
}
|
||||
})()
|
||||
})
|
||||
|
||||
// Escaneo diario de leads inactivos (recordatorio vía update en el item)
|
||||
|
||||
Reference in New Issue
Block a user