¿Cómo funciona?
El sistema de notificaciones de cambios te permite recibir automáticamente webhooks cuando se producen cambios en contratos. Esto te evita tener que hacer polling constante y mantiene tus sistemas sincronizados en tiempo real.
💡 Funcionamiento: Las notificaciones se envían automáticamente cuando cambia alguna de las columnas configuradas por el administrador del sistema. Actualmente, las columnas monitorizadas son: Estado y Subestado. Si esta lista de columnas cambia en el futuro, se comunicará a través de esta documentación.
📊 Estados y Subestados del Contrato
Los contratos pasan por diferentes estados durante su ciclo de vida. A continuación se describen todos los posibles estados y subestados que puede tener un contrato en Imagina Energía:
💡 Nota: Las notificaciones incluyen tanto los IDs numéricos (valor_anterior, valor_nuevo) como las descripciones legibles (descripcion_anterior, descripcion_nueva) de estados y subestados. Esto facilita la comprensión sin necesidad de mantener un mapeo en tu sistema.
Estados del Contrato
🟡 Estado: Pendiente (ID: 1)
| ID | Subestado | Descripción |
|---|---|---|
| 1 | Pendiente de firma | Contrato pendiente de firma por el cliente |
| 2 | Borrador | El primer estado que tienen un contrato antes de pasar a pendiente, no llega a los agentes |
| 23 | Pendiente sin revisar | Contrato pendiente de revisar por la comercializadora |
| 6 | Incidencia | El contrato tiene una incidencia que se ha declarado desde Neuro |
| 28 | Incidencia respondida | El contrato tenía una incidencia y ha sido respondida por el Agente |
🔵 Estado: Activable (ID: 2)
| ID | Subestado | Descripción |
|---|---|---|
| 6 | Incidencia | El contrato tiene una incidencia que se ha declarado desde Neuro |
| 28 | Incidencia respondida | El contrato tenía una incidencia y ha sido respondida por el Agente |
| 20 | Pendiente | El contrato ha sido firmado por el cliente y revisado por la comercializadora, pendiente solicitar switching |
| 27 | Pendiente de solicitud | Switching generado, pendiente de solicitud a distribución |
| 3 | Solicitado | Switching solicitado, esperando respuesta de distribuidora |
| 4 | Aceptado | Switching respondido por distribuidora, cambio aceptado, pendiente activación del cambio |
| 5 | Rechazado | Switching respondido por distribuidora, cambio rechazado |
| 8 | Anulación solicitada | Switching, se ha solicitado a la distribuidora la anulación de la solicitud |
| 31 | Incidencia en Campo | Switching, la distribuidora informa de que la solicitud con intervención en campo tiene una incidencia |
| 21 | Pendiente fecha activación | SIN USO |
🟢 Estado: Activo (ID: 3)
| ID | Subestado | Descripción |
|---|---|---|
| 9 | Activo | Contrato activo en operación normal |
| 10 | Saliente | Switching saliente informado por la distribuidora. El cliente va a cambiar de comercializadora |
| 11 | Modificación | Switching modificación solicitada a distribución en contrato de acceso (cambio potencia, autoconsumo, etc) |
| 26 | Modificación Rechazada | Switching modificación rechazada por distribución |
| 30 | Reposición solicitada | Switching solicitud de reposición (Switching 3.0) |
| 12 | Corte | Switching corte solicitado a la distribuidora |
| 14 | Cortado | Punto de suministro con servicio cortado |
| 13 | Baja | Contrato de suministro con baja solicitada |
| 22 | Renovado | SIN USO |
🔴 Estado: Cancelado (ID: 4)
| ID | Subestado | Descripción |
|---|---|---|
| 15 | Anulado | Contrato cancelado |
| 16 | Firma rechazada | El cliente rechaza firmar el contrato (Marca manual por parte de la comercializadora) |
| 24 | Scoring rechazado | Se rechaza el scoring y el contrato se cancela |
⚫ Estado: Finalizado (ID: 5)
| ID | Subestado | Descripción |
|---|---|---|
| 29 | Repuesto | Repuesto a la comercializadora anterior (Switching 3.0) |
| 25 | Desistimiento | Desistimiento solicitado |
| 17 | Aceptado por e1 | Desistimiento aceptado |
| 19 | Finalizado | Contrato finalizado |
💡 Ejemplo de Uso
Cuando recibas una notificación con cambio de estado/subestado:
{
"cambios": [
{
"campo": "Estado",
"campo_tecnico": "id_estado",
"valor_anterior": 2,
"valor_nuevo": 3,
"descripcion_anterior": "Pendiente",
"descripcion_nueva": "Activable"
},
{
"campo": "Subestado",
"campo_tecnico": "id_subestado",
"valor_anterior": 5,
"valor_nuevo": 12,
"descripcion_anterior": "Pendiente sin revisar",
"descripcion_nueva": "Solicitado"
}
]
}
El sistema proporciona automáticamente las descripciones legibles, por lo que puedes usar directamente descripcion_anterior y descripcion_nueva para mostrar información al usuario sin necesidad de mantener un mapeo de IDs en tu aplicación.
En este ejemplo, el contrato pasó de "Pendiente → Pendiente sin revisar" a "Activable → Solicitado", lo que indica que el switching fue solicitado a la distribuidora.
✅ Ventaja: Ya no necesitas mantener un catálogo de estados/subestados en tu sistema. Las descripciones se obtienen automáticamente de las tablas public.estados_contrato_suministro y public.subestados_contrato_suministro de la base de datos.
📝 Paso 1: Suscribirse a Cambios
Cuando creas un contrato, puedes proporcionar tu endpoint para recibir notificaciones de cambios en las columnas monitorizadas por el sistema.
Ejemplo de petición de alta de contrato:
{
"contrato": {
"nombre": "Juan Pérez",
"email": "juan@example.com",
...resto de campos del contrato...
},
"url_notificaciones_cambios_contrato": "https://tu-sistema.com/webhooks/contratos"
}
✅ Columnas monitorizadas: El sistema actualmente monitoriza los cambios en las columnas Estado y Subestado. Esta configuración es gestionada por el administrador del sistema. Cualquier cambio en la lista de columnas monitorizadas será comunicado a través de esta documentación.
📬 Paso 2: Recibir Notificaciones
Cuando una columna monitorizada cambia, recibirás un POST en tu endpoint con el siguiente formato:
{
"tabla": "contratos_suministro",
"tipo_evento": "UPDATE",
"timestamp": "2025-11-26T16:21:54.298664Z",
"contrato": {
"id": 543911,
"codigo": "543911555"
},
"cambios": [
{
"campo": "Estado",
"campo_tecnico": "id_estado",
"valor_anterior": 2,
"valor_nuevo": 3,
"descripcion_anterior": "Pendiente",
"descripcion_nueva": "Activable"
},
{
"campo": "Subestado",
"campo_tecnico": "id_subestado",
"valor_anterior": 5,
"valor_nuevo": 12,
"descripcion_anterior": "Pendiente sin revisar",
"descripcion_nueva": "Solicitado"
}
],
"_metadata": {
"notification_id": 5,
"attempt_number": 1
},
"_callback_signature": {
"version": "v1",
"algorithm": "HS256",
"signature": "I85mBPGW1PQ92Upe6qrFnY-dzzw7_7wljgYURIGjcTw",
"timestamp": "1764174114"
}
}
| Campo | Tipo | Descripción |
|---|---|---|
| tabla | String | Tabla de origen del evento ("contratos_suministro") |
| tipo_evento | String | Tipo de evento ("UPDATE") |
| timestamp | String (ISO 8601) | Fecha y hora del evento en formato UTC |
| contrato.id | Integer | ID interno del contrato en Imagina Energía |
| contrato.codigo | String | Código del contrato (identificador visible) |
| cambios | Array | Lista de todos los cambios ocurridos en esta actualización |
| cambios[].campo | String | Nombre legible del campo modificado (ej: "Estado", "Subestado") |
| cambios[].campo_tecnico | String | Nombre técnico del campo en base de datos (ej: "id_estado") |
| cambios[].valor_anterior | Integer/String | Valor anterior del campo (puede ser null) |
| cambios[].valor_nuevo | Integer/String | Nuevo valor del campo |
| cambios[].descripcion_anterior | String | Solo para id_estado/id_subestado: Descripción legible del valor anterior (ej: "Pendiente", "Activo") |
| cambios[].descripcion_nueva | String | Solo para id_estado/id_subestado: Descripción legible del valor nuevo (ej: "Activable", "Solicitado") |
| _metadata.notification_id | Integer | ID único de esta notificación (útil para detectar duplicados) |
| _metadata.attempt_number | Integer | Número de intento de entrega (1 = primer intento, 2+ = reintento) |
| _callback_signature | Object | Información de la firma HMAC-SHA256 para validación de autenticidad |
⚙️ Paso 3: Implementar tu Endpoint
Tu endpoint debe:
- ✅ Aceptar peticiones POST
- ✅ Validar la firma HMAC-SHA256 (ver guía de seguridad)
- ✅ Responder con código 200-299 para confirmar recepción
- ✅ Procesar las notificaciones de forma idempotente (pueden llegar duplicadas)
- ✅ Responder rápidamente (<30s) para evitar timeouts
Ejemplo de implementación (Python/Flask):
from flask import Flask, request, jsonify
app = Flask(__name__)
# Cache para evitar procesar notificaciones duplicadas
notificaciones_procesadas = set()
@app.route('/webhooks/contratos', methods=['POST'])
def webhook_cambios_contratos():
# 1. Validar firma HMAC-SHA256
payload = request.get_json()
signature = request.headers.get('X-Signature')
timestamp = request.headers.get('X-Signature-Timestamp')
if not validar_firma_callback(request.url, payload, signature, timestamp):
print("❌ Firma inválida - Rechazando notificación")
return jsonify({"error": "Firma inválida"}), 401
# 2. Extraer metadata
notification_id = payload.get('_metadata', {}).get('notification_id')
attempt_number = payload.get('_metadata', {}).get('attempt_number')
print(f"📥 Notificación recibida: ID={notification_id}, intento={attempt_number}")
# 3. Evitar duplicados (idempotencia)
if notification_id in notificaciones_procesadas:
print(f"⚠️ Notificación {notification_id} ya procesada - Ignorando")
return jsonify({"status": "ok", "message": "Duplicado"}), 200
# 4. Procesar cambios
contrato_id = payload['contrato']['id']
contrato_codigo = payload['contrato']['codigo']
cambios = payload['cambios']
for cambio in cambios:
campo = cambio['campo'] # Nombre legible: "Estado", "Subestado"
campo_tecnico = cambio['campo_tecnico'] # Nombre técnico: "id_estado", "id_subestado"
valor_nuevo = cambio['valor_nuevo']
descripcion_nueva = cambio.get('descripcion_nueva') # Descripción legible (si aplica)
print(f"🔄 Contrato {contrato_codigo}: {campo} cambió a {valor_nuevo} ({descripcion_nueva})")
# Aquí va tu lógica de negocio
# Puedes usar tanto el ID numérico como la descripción
if campo == 'Estado' and descripcion_nueva == 'Activo':
activar_servicios_internos(contrato_codigo)
elif campo == 'Subestado' and descripcion_nueva == 'Solicitado':
notificar_switching_solicitado(contrato_codigo)
# 5. Marcar como procesada
notificaciones_procesadas.add(notification_id)
# 6. Responder con éxito
return jsonify({"status": "ok"}), 200
🔒 Seguridad
⚠️ Crítico: Siempre valida la firma HMAC-SHA256 de las notificaciones para garantizar que provienen de Imagina Energía. Consulta la guía completa de seguridad aquí →
🔄 Sistema de Reintentos
Nuestro sistema garantiza la entrega de notificaciones mediante reintentos automáticos:
Configuración de Reintentos
- Frecuencia: Se ejecuta cada 30 minutos
- Condición de reintento: Si tu endpoint no respondió con código 200
- Máximo de intentos: 10 intentos por defecto (configurable)
- Sin backoff exponencial: Todos los fallos pendientes se reintentan en cada ejecución
Ejemplo de flujo de reintentos:
| Momento | Acción | attempt_number | Estado |
|---|---|---|---|
| 14:00 | Primera notificación | 1 | ❌ Tu servidor responde 500 |
| 14:30 | Primer reintento | 2 | ❌ Tu servidor sigue caído |
| 15:00 | Segundo reintento | 3 | ✅ Tu servidor responde 200 |
| - | Fin de reintentos | - | ✅ Notificación entregada |
💡 Detección de reintentos: Tu endpoint puede identificar reintentos comprobando si _metadata.attempt_number > 1. Usa _metadata.notification_id para evitar procesar duplicados.
💡 Mejores Prácticas
- 🔐 Valida siempre la firma: Nunca confíes en notificaciones sin validar la firma HMAC-SHA256
- 🔁 Implementa idempotencia: Usa _metadata.notification_id para detectar duplicados y procesar cada notificación solo una vez
- ⚡ Responde rápido: Confirma recepción rápidamente (200 OK) y procesa asincrónicamente si es necesario
- 📊 Monitoriza: Registra los attempt_number altos para detectar problemas en tu endpoint
- 🔍 Logging: Guarda un log con notification_id, attempt_number y timestamp
- 🚨 Alertas: Configura alertas si recibes muchos reintentos (puede indicar problemas en tu sistema)
- 🔒 HTTPS obligatorio: Tu endpoint debe usar HTTPS en producción
- ⏱️ Timeout: Nuestras peticiones tienen timeout de 30 segundos - responde antes
❓ Preguntas Frecuentes
Ventaja: Para estos campos, recibirás automáticamente las descripciones legibles en descripcion_anterior y descripcion_nueva, evitando que tengas que mantener un catálogo de estados en tu sistema.
if notification_id in notificaciones_procesadas:
return {"status": "ok"}, 200 # Ya procesada, ignorar
notificaciones_procesadas.add(notification_id)
- attempt_number: 1 = Primer intento (envío inicial)
- attempt_number: 2 = Primer reintento
- attempt_number: 3 = Segundo reintento (último intento por defecto)