Transferencia asíncrona de leads desde Next.js a 1C-Bitrix: Un patrón sin colas ni workers
El envío síncrono de leads al CRM vía REST API provoca problemas críticos: el usuario espera la respuesta hasta que se completa la solicitud externa, y la inestabilidad de la API de 1C-Bitrix genera timeouts y errores. Proponemos una solución en Next.js 16 que elimina colas y procesos en segundo plano. La implementación guarda el lead en una base de datos PostgreSQL local, responde instantáneamente al usuario y maneja la integración con Bitrix mediante after() —un mecanismo para procesamiento en segundo plano después de enviar la respuesta HTTP.
Arquitectura sin colas: Tres capas de responsabilidad
Principio clave: separar las operaciones en partes síncronas y asíncronas. Al recibir el formulario:
- Validación de datos mediante zod
- Guardar en PostgreSQL con bitrix_id=NULL
- Respuesta instantánea 200 OK
- Envío en segundo plano a Bitrix mediante after()
Esto garantiza:
- Independencia de la estabilidad del REST API de Bitrix
- Sin bloqueo de la interfaz de usuario
- Posibilidad de procesar manualmente los leads no entregados mediante SELECT * FROM leads WHERE bitrix_id IS NULL
La característica clave del enfoque es prescindir de Redis/BullMQ. Todas las operaciones caben en una ruta API de Next.js, usando el after() integrado para el procesamiento posterior a la respuesta. Esto reduce la complejidad de la infraestructura y simplifica el despliegue en VPS mediante systemd/nginx.
after() en Next.js 16: Implementación técnica
El método after() resuelve el problema del "dispara y olvida" en entornos sin servidor. A diferencia de Promise.resolve().then(), asegura la ejecución de la tarea incluso después de enviar la respuesta, sin terminar el proceso hasta que la operación se complete. Ejemplo de procesamiento de formulario:
export async function POST(request: Request) {
// ... validatsiya and antidubl
const [lead] = await pgQuery(...);
after(async () => {
try {
const bitrixId = await sendToBitrix24(payload);
await pgQuery(
`UPDATE leads SET bitrix_id = $1 WHERE id = $2`,
[bitrixId, lead.id]
);
} catch (error) {
console.error("[Leads API] Error otpravki in Bitriks:", error);
}
});
return Response.json({ ok: true, id: lead.id });
}
Matizes clave:
- Antidúplicación implementada mediante una ventana de 10 minutos en PostgreSQL, no a través de Bitrix (el CRM acepta duplicados sin advertencia)
- bitrix_id almacenado como text nullable —refleja la naturaleza asíncrona de la operación
- Errores de integración registrados en journalctl, sin afectar la respuesta al usuario
Cliente HTTP confiable para 1C-Bitrix
Parámetros críticos para un trabajo estable con una API inestable:
- MAX_ATTEMPTS = 2 —equilibrio entre fiabilidad y carga
- REQUEST_TIMEOUT_MS = 8000 —por encima de la latencia p95 de Bitrix (400-700 ms)
- RETRY_DELAY_MS = 1500 —backoff lineal sin complejidad exponencial
La implementación del cliente incluye:
async function bitrixRequest(method: string, payload: Record<string, unknown>) {
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
try {
const response = await fetch(url, {
signal: controller.signal,
// ...
});
const text = await response.text();
const json = text ? JSON.parse(text) : {};
if (!response.ok || json.error) {
throw new Error(json.error_description || `HTTP ${response.status}`);
}
return json;
} catch (error) {
if (attempt >= MAX_ATTEMPTS) throw error;
await new Promise(r => setTimeout(r, RETRY_DELAY_MS));
} finally {
clearTimeout(timer);
}
}
}
Tres puntos clave:
- Análisis manual mediante response.text() en lugar de response.json() —evita errores de Bitrix con respuestas vacías
- Manejo de dos tipos de errores: estados HTTP y errores internos del CRM
- Limpieza garantizada del temporizador en finally —previene fugas de memoria
Características de autenticación y esquema de datos
Para la integración, se elige webhook entrante en lugar de OAuth:
- Sin dependencia del Marketplace y aprobaciones
- Gestión simple de la variable de entorno BITRIX_WEBHOOK_URL
- Scopes configurados en el panel de administración de Bitrix
El riesgo de filtración de token en la URL se minimiza con la regla: logs y código muestran solo el método API, no la URL completa.
Esquema de la tabla leads:
CREATE TABLE leads (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
phone text NOT NULL,
source text,
bitrix_id text,
created_at timestamptz NOT NULL DEFAULT now()
);
¿Por qué bitrix_id text, no integer?
- Bitrix devuelve el ID como string en JSON
- Sin necesidad de conversión de tipos
- UUID en PostgreSQL asegura idempotencia
Puntos clave
- Fuente única de verdad —BD local: el lead se guarda antes de contactar a Bitrix, asegurando los datos incluso en fallo del CRM
- Procesamiento posterior a la respuesta: after() reemplaza las colas, eliminando complejidad de infraestructura
- Manejo de errores: reintentos con timeouts y logging en journalctl aseguran observabilidad
- Antidúplicación a nivel de BD: ventana de 10 minutos por teléfono previene duplicados independientemente del CRM
- Autenticación segura: webhook requiere control estricto del logging de URL
Este patrón es adecuado para proyectos con hasta 50 solicitudes por minuto. Para sistemas de alta carga, agregar monitoreo mediante SELECT COUNT(*) FROM leads WHERE bitrix_id IS NULL y reintentos automáticos vía cron. En la implementación actual, el manejo manual de errores es preferible a los reintentos automáticos —reduce el riesgo de duplicación de leads durante fallos temporales.
— Editorial Team
Aún no hay comentarios.