erp-transportistas-backend-v2/src/modules/whatsapp/controllers/whatsapp.controller.ts

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;
}