424 lines
11 KiB
TypeScript
424 lines
11 KiB
TypeScript
/**
|
|
* WhatsApp Controller
|
|
* ERP Transportistas
|
|
*
|
|
* REST API endpoints for WhatsApp notifications.
|
|
* Sprint: S5 - TASK-007
|
|
* Module: WhatsApp Integration
|
|
*/
|
|
|
|
import { Request, Response, Router } from 'express';
|
|
import { DataSource } from 'typeorm';
|
|
import {
|
|
WhatsAppNotificationService,
|
|
NotificationRequest,
|
|
} from '../services/whatsapp-notification.service';
|
|
import { TipoTemplateTransporte } from '../templates/transport-templates';
|
|
|
|
/**
|
|
* Create WhatsApp controller with DataSource injection
|
|
*/
|
|
export function createWhatsAppController(dataSource: DataSource): Router {
|
|
const router = Router();
|
|
const notificationService = new WhatsAppNotificationService();
|
|
|
|
/**
|
|
* POST /configurar
|
|
* Configure WhatsApp API credentials
|
|
*/
|
|
router.post('/configurar', async (req: Request, res: Response) => {
|
|
try {
|
|
const { apiUrl, accessToken, phoneNumberId, businessAccountId } = req.body;
|
|
|
|
if (!apiUrl || !accessToken || !phoneNumberId || !businessAccountId) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos: apiUrl, accessToken, phoneNumberId, businessAccountId',
|
|
});
|
|
}
|
|
|
|
notificationService.configure({
|
|
apiUrl,
|
|
accessToken,
|
|
phoneNumberId,
|
|
businessAccountId,
|
|
});
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
message: 'WhatsApp API configurado correctamente',
|
|
enabled: notificationService.isEnabled(),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /estado
|
|
* Get service status
|
|
*/
|
|
router.get('/estado', async (_req: Request, res: Response) => {
|
|
try {
|
|
res.json({
|
|
success: true,
|
|
data: {
|
|
enabled: notificationService.isEnabled(),
|
|
templatesDisponibles: Object.values(TipoTemplateTransporte),
|
|
},
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /enviar
|
|
* Send a single notification
|
|
*/
|
|
router.post('/enviar', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, tipoTemplate, parametros, metadata } = req.body;
|
|
|
|
if (!telefono || !tipoTemplate || !parametros) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos: telefono, tipoTemplate, parametros',
|
|
});
|
|
}
|
|
|
|
// Validate template type
|
|
if (!Object.values(TipoTemplateTransporte).includes(tipoTemplate)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: `Template inválido. Opciones: ${Object.values(TipoTemplateTransporte).join(', ')}`,
|
|
});
|
|
}
|
|
|
|
const request: NotificationRequest = {
|
|
telefono,
|
|
tipoTemplate,
|
|
parametros,
|
|
metadata,
|
|
};
|
|
|
|
const result = await notificationService.enviarNotificacion(request);
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /enviar-lote
|
|
* Send batch notifications
|
|
*/
|
|
router.post('/enviar-lote', async (req: Request, res: Response) => {
|
|
try {
|
|
const { notificaciones } = req.body;
|
|
|
|
if (!notificaciones || !Array.isArray(notificaciones) || notificaciones.length === 0) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Se requiere un array de notificaciones',
|
|
});
|
|
}
|
|
|
|
// Validate each notification
|
|
for (const notif of notificaciones) {
|
|
if (!notif.telefono || !notif.tipoTemplate || !notif.parametros) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Cada notificación requiere: telefono, tipoTemplate, parametros',
|
|
});
|
|
}
|
|
if (!Object.values(TipoTemplateTransporte).includes(notif.tipoTemplate)) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: `Template inválido: ${notif.tipoTemplate}`,
|
|
});
|
|
}
|
|
}
|
|
|
|
const result = await notificationService.enviarLote(notificaciones);
|
|
|
|
res.json({
|
|
success: true,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
// ==========================================
|
|
// Transport-Specific Endpoints
|
|
// ==========================================
|
|
|
|
/**
|
|
* POST /viaje-asignado
|
|
* Notify operator of trip assignment
|
|
*/
|
|
router.post('/viaje-asignado', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, nombreOperador, origen, destino, fecha, horaCita, folioViaje } = req.body;
|
|
|
|
if (!telefono || !nombreOperador || !origen || !destino || !fecha || !horaCita || !folioViaje) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarViajeAsignado(telefono, {
|
|
nombreOperador,
|
|
origen,
|
|
destino,
|
|
fecha,
|
|
horaCita,
|
|
folioViaje,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /viaje-confirmado
|
|
* Notify client of shipment confirmation
|
|
*/
|
|
router.post('/viaje-confirmado', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, nombreCliente, folio, unidad, operador, fecha, eta, codigoTracking } = req.body;
|
|
|
|
if (!telefono || !nombreCliente || !folio || !unidad || !operador || !fecha || !eta || !codigoTracking) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarViajeConfirmado(telefono, {
|
|
nombreCliente,
|
|
folio,
|
|
unidad,
|
|
operador,
|
|
fecha,
|
|
eta,
|
|
codigoTracking,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /eta-actualizado
|
|
* Notify ETA update
|
|
*/
|
|
router.post('/eta-actualizado', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, folio, nuevoEta, motivo } = req.body;
|
|
|
|
if (!telefono || !folio || !nuevoEta || !motivo) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos: telefono, folio, nuevoEta, motivo',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarEtaActualizado(telefono, {
|
|
folio,
|
|
nuevoEta,
|
|
motivo,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /viaje-completado
|
|
* Notify trip completion
|
|
*/
|
|
router.post('/viaje-completado', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, nombreCliente, folio, destino, fechaHora, receptor } = req.body;
|
|
|
|
if (!telefono || !nombreCliente || !folio || !destino || !fechaHora || !receptor) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarViajeCompletado(telefono, {
|
|
nombreCliente,
|
|
folio,
|
|
destino,
|
|
fechaHora,
|
|
receptor,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /alerta-retraso
|
|
* Notify delay alert
|
|
*/
|
|
router.post('/alerta-retraso', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, nombre, folio, etaOriginal, nuevoEta, motivo } = req.body;
|
|
|
|
if (!telefono || !nombre || !folio || !etaOriginal || !nuevoEta || !motivo) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarAlertaRetraso(telefono, {
|
|
nombre,
|
|
folio,
|
|
etaOriginal,
|
|
nuevoEta,
|
|
motivo,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /asignacion-carrier
|
|
* Notify carrier of service request
|
|
*/
|
|
router.post('/asignacion-carrier', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, nombreCarrier, origen, destino, fecha, tarifa } = req.body;
|
|
|
|
if (!telefono || !nombreCarrier || !origen || !destino || !fecha || !tarifa) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarAsignacionCarrier(telefono, {
|
|
nombreCarrier,
|
|
origen,
|
|
destino,
|
|
fecha,
|
|
tarifa,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
/**
|
|
* POST /recordatorio-mantenimiento
|
|
* Notify maintenance reminder
|
|
*/
|
|
router.post('/recordatorio-mantenimiento', async (req: Request, res: Response) => {
|
|
try {
|
|
const { telefono, unidad, tipoMantenimiento, fechaLimite, kmActual, kmProgramado } = req.body;
|
|
|
|
if (!telefono || !unidad || !tipoMantenimiento || !fechaLimite || !kmActual || !kmProgramado) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
error: 'Faltan campos requeridos',
|
|
});
|
|
}
|
|
|
|
const result = await notificationService.notificarRecordatorioMantenimiento(telefono, {
|
|
unidad,
|
|
tipoMantenimiento,
|
|
fechaLimite,
|
|
kmActual,
|
|
kmProgramado,
|
|
});
|
|
|
|
res.json({
|
|
success: result.success,
|
|
data: result,
|
|
});
|
|
} catch (error) {
|
|
res.status(500).json({
|
|
success: false,
|
|
error: (error as Error).message,
|
|
});
|
|
}
|
|
});
|
|
|
|
return router;
|
|
}
|