Saltar al contenido principal

SMSc - Aplicación del Centro de Servicios SMS

Aplicación del Centro SMS construida con Phoenix/Elixir, que proporciona una cola de mensajes centralizada y una API REST para el enrutamiento y la entrega de SMS.

Documentación

Documentación Principal

Documentación de Cumplimiento

Rendimiento

Descripción General de la Arquitectura

El núcleo de SMS_C proporciona una cola de mensajes independiente del protocolo y una API REST. Los frontends SMSC externos (SMPP, IMS, SS7/MAP) se conectan como puertas de enlace independientes que se comunican con el núcleo a través de la API REST.

Flujo de Mensajes

Flujo de Mensajes Salientes (MT - Móvil Terminado)

Flujo de Mensajes Entrantes (MO - Móvil Originado)

Características Clave

1. Diseño Independiente del Protocolo

  • El núcleo SMS_C maneja la persistencia de mensajes, el enrutamiento y la API
  • Los frontends externos (SMPP, IMS, SS7/MAP) manejan la comunicación específica del protocolo
  • Todos los frontends se comunican a través de una API REST unificada
  • Agregar nuevos protocolos sin cambiar el núcleo
  • Cada uno puede escalarse de forma independiente

2. Enrutamiento de Mensajes

  • Motor de enrutamiento dinámico con configuración en tiempo de ejecución
  • Enrutamiento basado en prefijos (números llamados/llamantes)
  • Filtrado de SMSC y tipo de fuente (IMS/Circuito Conmutado/SMPP)
  • Enrutamiento basado en prioridad con balanceo de carga basado en peso
  • Capacidades de enrutamiento de respuesta automática y eliminación de mensajes
  • Control de cargos por ruta
  • Interfaz web para gestión de rutas
  • Actualizaciones de ruta en tiempo real sin interrupción del servicio

📖 Consulta la Guía de Enrutamiento de SMS para documentación completa

3. Lógica de Reintento con Retroceso Exponencial

  • Reintento automático en caso de fallo de entrega
  • Retroceso exponencial: 1min, 2min, 4min, 8min, etc.
  • Intentos de reintento máximos configurables
  • Manejo de expiración de mensajes
  • Seguimiento de reintentos por mensaje

Guía de Operaciones

Puntos de Acceso:

  • API REST: https://localhost:8443 (o http://localhost:8080 sin TLS)
  • Panel de Control: https://localhost:8086
  • Documentación de API (Swagger UI): https://localhost:8443/api/docs

Iniciar Frontends Externos: Cada frontend de protocolo es una aplicación independiente. Consulta la documentación de cada frontend para instrucciones de inicio.

Configuración

Toda la configuración se gestiona directamente en config/runtime.exs. No se utilizan variables de entorno.

Configuración del Núcleo

Actualmente no se utilizan variables de entorno para la configuración de la aplicación núcleo. Los puertos del servidor y los nombres de host se configuran en config/runtime.exs:

  • Servidor API: Puerto 8443 (HTTPS), escuchando en 0.0.0.0
  • Panel de Control: Puerto 80 (HTTP), escuchando en 0.0.0.0

Configuración de la Base de Datos

Los ajustes de la base de datos se configuran en config/runtime.exs:

  • Nombre de usuario: omnitouch
  • Contraseña: omnitouch2024
  • Nombre del host: localhost
  • Puerto: 3306
  • Nombre de la base de datos: smsc_new
  • Tamaño del grupo: 1

Configuración del Clúster

Los ajustes del clúster se configuran en config/runtime.exs:

  • Nodos del clúster: "" (vacío - sin agrupamiento por defecto)
  • Consulta de clúster DNS: nil

Configuración de la Cola de Mensajes

Los ajustes de la cola de mensajes se configuran en config/runtime.exs:

  • Tiempo de carta muerta: 1440 minutos (24 horas antes de que el mensaje expire)

Integración de Cargos

Los ajustes de cargos se configuran en config/runtime.exs:

  • URL: http://localhost:2080/jsonrpc
  • Inquilino: mnc057.mcc505.3gppnetwork.org
  • Destino: 55512341234
  • Fuente: 00101900000257
  • Asunto: 00101900000257
  • Cuenta: 00101900000257

Configuración de Enrutamiento de SMS

El sistema de enrutamiento de SMS utiliza rutas dinámicas respaldadas por bases de datos que pueden ser gestionadas a través de la interfaz web o el archivo de configuración. Las rutas se cargan desde config/runtime.exs en el primer inicio.

Ejemplo de Configuración:

config :sms_c, :sms_routes, [
%{
called_prefix: "+44",
dest_smsc: "InternationalGW",
weight: 100,
priority: 100,
description: "SMS Internacional del Reino Unido",
enabled: true
},
%{
called_prefix: "1900",
dest_smsc: "PremiumGW",
charged: :yes,
priority: 50,
description: "Números Premium de EE. UU.",
enabled: true
}
]

Características:

  • Coincidencia basada en prefijos (números llamados/llamantes)
  • Filtrado de SMSC de origen y tipo
  • Enrutamiento basado en prioridad y peso
  • Capacidades de respuesta automática y eliminación
  • Control de cargos por ruta
  • Gestión en tiempo de ejecución a través de la interfaz web en /routing

📖 Consulta la Guía de Enrutamiento de SMS para documentación completa, ejemplos y referencia de API.

Puntos Finales de la API REST

Operaciones de Cola de Mensajes

Enviar SMS (Crear Mensaje)

POST /api/messages
Content-Type: application/json

{
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "¡Hola, Mundo!",
"source_smsc": "web-app",
"dest_smsc": "smpp-provider", # Opcional - el motor de enrutamiento asigna si es nulo
"tp_dcs_character_set": "gsm7", # Opcional: gsm7, 8bit, latin1, ucs2
"tp_dcs_coding_group": "general_data_coding",
"expires": "2025-10-17T10:30:00Z" # Opcional - por defecto 24h desde ahora
}

Campos Requeridos:

  • destination_msisdn - Número de teléfono de destino
  • message_body - Contenido del texto del mensaje
  • source_msisdn - Número de teléfono de origen
  • source_smsc - Identificador del sistema de origen

Campos Opcionales:

  • dest_smsc - Puerta de enlace de destino (el motor de enrutamiento asigna si no se proporciona)
  • source_imsi, dest_imsi - Identificadores IMSI
  • tp_dcs_character_set - Codificación de caracteres (gsm7, 8bit, latin1, ucs2)
  • tp_dcs_coding_group - Grupo de codificación DCS
  • tp_dcs_compressed - Indicador de compresión (booleano)
  • tp_dcs_has_message_class - Indicador de clase de mensaje (booleano)
  • tp_dcs_message_class - Valor de clase de mensaje
  • tp_user_data_header - Encabezado de datos de usuario (mapa)
  • message_part_number, message_parts - Campos de mensaje multipart
  • expires - Marca de tiempo de expiración (por defecto 24 horas)
  • deliver_after - Marca de tiempo de entrega retrasada
  • deadletter, raw_data_flag, raw_sip_flag - Indicadores booleanos

Respuesta:

{
"status": "success",
"data": {
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"dest_smsc": "smpp-provider",
"message_body": "¡Hola, Mundo!",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
}

Obtener Mensajes para SMSC

GET /api/messages/get_by_smsc?smsc=mi-nombre-smsc

Devuelve todos los mensajes no entregados donde:

  • destination_smsc es nulo O coincide con el nombre SMSC proporcionado
  • El mensaje no ha expirado
  • Listo para enviar (deliver_after es nulo o está en el pasado)

Respuesta:

{
"status": "success",
"data": [
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "Hola",
"destination_smsc": "mi-nombre-smsc",
"delivery_attempts": 0
}
]
}

Listar Mensajes con Filtrado Opcional de SMSC

# Listar todos los mensajes en la cola
GET /api/messages

# Listar mensajes para SMSC específico (con filtrado de encabezados)
GET /api/messages
SMSc: mi-nombre-smsc

Sin Encabezado SMSc: Devuelve todos los mensajes en la cola independientemente del estado de entrega o expiración.

Con Encabezado SMSc: Devuelve mensajes no entregados donde:

  • dest_smsc coincide con el valor del encabezado O dest_smsc es nulo
  • deliver_time es nulo (no entregado aún)
  • deliver_after es nulo o antes/igual al tiempo actual (listo para entregar)
  • expires es después del tiempo actual (no ha expirado)
  • Ordenado por tiempo de inserción (el más antiguo primero)

Nota: El enfoque del encabezado SMSc permite que los frontends externos consulten sus mensajes utilizando el mismo patrón de punto final, con el encabezado controlando el comportamiento de filtrado.

Respuesta:

[
{
"id": 123,
"source_msisdn": "+1234567890",
"destination_msisdn": "+0987654321",
"message_body": "¡Hola, Mundo!",
"dest_smsc": "mi-nombre-smsc",
"deliver_time": null,
"delivery_attempts": 0,
"expires": "2025-10-17T10:30:00Z",
"inserted_at": "2025-10-16T10:30:00Z"
}
]

Obtener Mensaje Único

GET /api/messages/{id}

Actualizar Mensaje

PATCH /api/messages/{id}
Content-Type: application/json

{
"status": "delivered",
"delivered_at": "2025-10-16T10:30:00Z"
}

Eliminar SMS

DELETE /api/messages/{id}

Manejar Fallo de Entrega (Incrementar Contador de Reintentos)

Cuando un mensaje falla temporalmente en la entrega, incrementa el contador de intentos de entrega y programa un reintento con retroceso exponencial.

Método 1: Usando PUT (Recomendado)

# Simple y semántico - PUT indica actualizar el estado de entrega
PUT /api/messages/{id}

Método 2: Usando Punto Final Explícito

# Punto final alternativo explícito
POST /api/messages/{id}/increment_delivery_attempt

Ambos métodos incrementan delivery_attempts y establecen un retraso de retroceso exponencial a través de deliver_after:

IntentoFórmula de RetrocesoRetrasoTiempo Total
2^1 minutos2 min2 min
2^2 minutos4 min6 min
2^3 minutos8 min14 min
2^4 minutos16 min30 min
2^5 minutos32 min1h 2min
2^6 minutos64 min2h 6min

Respuesta:

{
"id": 123,
"delivery_attempts": 1,
"deliver_after": "2025-10-20T19:05:00Z",
"deliver_time": null,
"expires": "2025-10-21T19:03:00Z",
...
}

Nota: Los mensajes con deliver_after futuro se filtran automáticamente de las solicitudes GET hasta que expire el período de retroceso.

Actualizar Mensaje (Actualización Parcial)

Para actualizar campos específicos del mensaje (comportamiento inalterado):

PATCH /api/messages/{id}
Content-Type: application/json

{
"dest_smsc": "puerta-actualizada",
"status": "delivered"
}

Importante: PUT y PATCH se comportan de manera diferente:

  • PUT → Incrementa los intentos de entrega con retroceso (no se requiere cuerpo)
  • PATCH → Realiza actualizaciones parciales de campos (se requiere cuerpo)

Seguimiento de Salud del Frontend

El núcleo SMS_C rastrea la salud y disponibilidad de los frontends externos a través de un sistema de registro. Esto permite monitorear el tiempo de actividad del frontend, detectar fallos y mantener datos históricos de disponibilidad.

Nota: El registro del frontend NO se utiliza para la entrega o enrutamiento de mensajes. Los mensajes se enrutan en función del campo dest_smsc. El sistema de registro existe únicamente para el monitoreo de salud y visibilidad operativa.

Cómo Funciona el Registro del Frontend

Cada frontend externo (puerta de enlace SMPP, IMS, SS7/MAP) envía periódicamente un registro de latido al núcleo SMS_C:

  1. Intervalo de Latido: Los frontends deben registrarse cada 30-60 segundos
  2. Tiempo de Expiración: Los registros expiran después de 90 segundos sin una actualización
  3. Gestión Automática del Estado:
    • Nuevos frontends crean un nuevo registro de registro
    • Frontends activos existentes actualizan su registro (extiende la expiración)
    • Frontends expirados que vuelven a estar en línea crean un nuevo período de registro

Puntos Finales de Registro del Frontend

Registrar/Actualizar Frontend (Latido)
POST /api/frontends
Content-Type: application/json

{
"frontend_name": "puerta-sMPP-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"configuration": "{\"port\": 2775, \"system_id\": \"usuario_sMPP\"}"
}

Campos Requeridos:

  • frontend_name - Identificador único para la instancia del frontend
  • frontend_type - Tipo de frontend (SMPP, IMS, MAP, etc.)

Campos Opcionales:

  • ip_address - Dirección IP del frontend (detectada automáticamente desde la fuente de la solicitud si no se proporciona)
  • hostname - Nombre de host del servidor frontend
  • uptime_seconds - Segundos desde que se inició el frontend
  • configuration - Cadena JSON con la configuración específica del frontend

Nota: Si no se proporciona ip_address, el núcleo SMS_C utilizará automáticamente la IP de origen de la solicitud HTTP. Esto funciona tanto con conexiones directas como con solicitudes proxy (a través del encabezado X-Forwarded-For).

Respuesta:

{
"id": 42,
"frontend_name": "puerta-sMPP-1",
"frontend_type": "SMPP",
"ip_address": "10.0.1.5",
"hostname": "smpp-gw-01",
"uptime_seconds": 3600,
"status": "activo",
"last_seen_at": "2025-10-20T10:30:00Z",
"expires_at": "2025-10-20T10:31:30Z",
"inserted_at": "2025-10-20T10:00:00Z"
}
Listar Todos los Registros de Frontend
GET /api/frontends

Devuelve todos los registros de frontend (activos y expirados), ordenados por la actividad más reciente.

Listar Solo Frontends Activos
GET /api/frontends/active

Devuelve solo los frontends actualmente activos (no expirados).

Obtener Estadísticas del Frontend
GET /api/frontends/stats

Devuelve estadísticas resumidas:

{
"active": 5,
"expired": 12,
"unique_frontends": 8
}
Obtener Historial del Frontend
GET /api/frontends/history/{frontend_name}

Devuelve todos los registros históricos para un frontend específico, útil para analizar patrones de tiempo de actividad/inactividad.

Ejemplo:

GET /api/frontends/history/puerta-sMPP-1
Obtener Registro Específico
GET /api/frontends/{id}

Implementación en Frontends Externos

Los frontends externos deben implementar una tarea en segundo plano que envíe latidos:

Ejemplo (pseudocódigo):

import time
import requests

def send_heartbeat():
"""Enviar latido cada 30 segundos"""
while True:
try:
data = {
"frontend_name": "mi-puerta-sMPP",
"frontend_type": "SMPP",
"ip_address": get_local_ip(),
"hostname": get_hostname(),
"uptime_seconds": get_uptime()
}

response = requests.post(
"https://smsc-core:8443/api/frontends",
json=data,
timeout=5
)

if response.status_code in [200, 201]:
logger.debug("Latido enviado con éxito")
else:
logger.error(f"Fallo en el latido: {response.status_code}")

except Exception as e:
logger.error(f"Error en el latido: {e}")

time.sleep(30) # Enviar cada 30 segundos

# Iniciar latido en hilo en segundo plano
threading.Thread(target=send_heartbeat, daemon=True).start()

Monitoreo de Salud del Frontend

Panel de Control - La interfaz web en https://localhost:8086 muestra:

  • Frontends actualmente activos
  • Marca de tiempo de la última vez que se vio cada frontend
  • Seguimiento del tiempo de actividad
  • Disponibilidad histórica

Consultas API:

# Obtener todos los frontends activos
curl https://localhost:8443/api/frontends/active

# Comprobar si un frontend específico está activo
curl https://localhost:8443/api/frontends/history/puerta-sMPP-1 | jq '.[0].status'

# Obtener estadísticas de salud
curl https://localhost:8443/api/frontends/stats

Otros Puntos Finales

Estado

GET /api/status

Ubicaciones

GET /api/locations
POST /api/locations
GET /api/locations/{id}
PATCH /api/locations/{id}
DELETE /api/locations/{id}

Eventos SS7

GET /api/ss7_events
POST /api/ss7_events
GET /api/ss7_events/{id}
PATCH /api/ss7_events/{id}
DELETE /api/ss7_events/{id}

Cola de Mensajes MMS

GET /api/mms_message_queues
POST /api/mms_message_queues
GET /api/mms_message_queues/{id}
PATCH /api/mms_message_queues/{id}
DELETE /api/mms_message_queues/{id}

Rendimiento

El núcleo SMS_C ofrece un rendimiento excepcional utilizando Mnesia para el almacenamiento de mensajes en memoria con archivo automático a SQL para la retención a largo plazo de CDR.

Resultados de Referencia

Medido en Intel i7-8650U @ 1.90GHz (8 núcleos):

Rendimiento de Inserción de Mensajes:

  • insert_message (con enrutamiento): 1,750 msg/sec (0.58ms latencia promedio)
  • insert_message (simple): 1,750 msg/sec (0.57ms latencia promedio)
  • Capacidad de ~150 millones de mensajes por día

Rendimiento de Consulta:

  • get_messages_for_smsc: 800 msg/sec (1.25ms promedio)
  • list_message_queues: Acceso rápido en memoria
  • Uso de memoria: 62 KB por operación de inserción

Arquitectura

Estrategia de Almacenamiento:

  • Mensajes Activos: Almacenados en Mnesia (en memoria + disco) para acceso ultrarrápido
  • Archivo de Mensajes: Archivado automáticamente a SQL para almacenamiento a largo plazo de CDR
  • Retención: Período de retención configurable (predeterminado: 24 horas)
  • Sin cuello de botella SQL: Todas las operaciones de mensajes activos evitan SQL

Configuración

El almacenamiento y la retención de mensajes se configuran en config/runtime.exs:

config :sms_c,
message_retention_hours: 24, # Archivar mensajes más antiguos de 24 horas
batch_insert_batch_size: 100, # Tamaño del lote de CDR para archivo SQL
batch_insert_flush_interval_ms: 100 # Intervalo de vaciado de CDR

Para orientación detallada sobre ajustes, consulta: docs/PERFORMANCE_TUNING.md

Monitoreo

Panel de Control - Interfaz web en https://localhost:8086

  • Ver cola de mensajes
  • Enviar mensajes de prueba
  • Gestionar enrutamiento de SMS (ver Guía de Enrutamiento)
  • Simular decisiones de enrutamiento
  • Ver recursos del sistema
  • Rastrear estadísticas de trabajadores por lotes

Estadísticas de Trabajadores por Lotes:

# Obtener estadísticas actuales del trabajador por lotes
SmsC.Messaging.BatchInsertWorker.stats()

Devuelve:

%{
total_enqueued: 10000,
total_flushed: 9900,
current_queue_size: 100,
last_flush_duration_ms: 45
}

Registros - Registros de la aplicación escritos en stdout

# Ver registros en tiempo real
tail -f log/dev.log

Solución de Problemas

Puerto Ya Está en Uso

# Encontrar proceso que usa el puerto
lsof -i :4000

# Matar el proceso
kill -9 <PID>

Frontend Externo No Conectando

Síntomas: Mensajes atascados en la cola, registros del frontend muestran errores de conexión

Verifica:

  • Verifica que API_BASE_URL esté configurado correctamente en el frontend
  • Comprueba que el núcleo SMS_C esté en ejecución y accesible
  • Revisa las reglas de red/firewall
  • Verifica la configuración del frontend

Solución:

# Probar conectividad API desde el frontend
curl http://localhost:4000/api/status

# Reiniciar frontend
export API_BASE_URL="http://localhost:4000"
# Iniciar aplicación frontend

Mensajes No Entregados

Síntomas: Mensajes permanecen no entregados, los intentos de reintento se incrementan

Verifica:

  1. Registros del frontend para errores de envío
  2. Conectividad de red externa
  3. Configuración del frontend (credenciales, direcciones)
  4. Compatibilidad del formato del mensaje

Ver mensajes fallidos:

# Obtener mensajes con intentos de reintento
curl https://localhost:8443/api/messages | jq '.data[] | select(.delivery_attempts > 0)'

Alta Latencia de Mensajes

Síntomas: Mensajes tardando más de lo esperado, acumulación en la cola

Verifica:

  1. Intervalo de sondeo del frontend (puede ser necesario disminuir para un sondeo más frecuente)
  2. Rendimiento de la base de datos
  3. Latencia de red hacia sistemas externos

Monitorear profundidad de la cola:

watch -n 5 'curl -s https://localhost:8443/api/messages | jq ".data | length"'