¿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

1. ¿Qué columnas se monitorizan?
Las columnas monitorizadas son configuradas por el administrador del sistema. Actualmente, el sistema monitoriza cambios en: Estado (id_estado) y Subestado (id_subestado). Si esta lista se actualiza en el futuro, se comunicará a través de esta documentación.

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.
2. ¿Qué pasa si mi endpoint está caído?
El sistema reintentará automáticamente cada 30 minutos hasta 3 veces. Si después de 10 intentos tu endpoint sigue sin responder correctamente, la notificación se marcará como fallida permanentemente. Deberás implementar lógica de reconciliación para casos extremos.
3. ¿Cómo evito procesar notificaciones duplicadas?
Usa el campo _metadata.notification_id como clave única. Mantén un registro (base de datos, cache, etc.) de los IDs procesados y verifica antes de procesar cada notificación. Ejemplo:
if notification_id in notificaciones_procesadas:
    return {"status": "ok"}, 200  # Ya procesada, ignorar
notificaciones_procesadas.add(notification_id)
4. ¿Qué significa attempt_number?
Es el número de intento de entrega de esta notificación específica:
  • attempt_number: 1 = Primer intento (envío inicial)
  • attempt_number: 2 = Primer reintento
  • attempt_number: 3 = Segundo reintento (último intento por defecto)
Puedes usar este campo para monitorizar problemas en tu endpoint. Si recibes muchos attempt_number > 1, indica que tu sistema no está respondiendo correctamente en el primer intento.
5. ¿Las notificaciones se agrupan por contrato?
Sí, si múltiples columnas de un mismo contrato cambian en la misma operación, recibirás una sola notificación con todos los cambios en el array cambios. Esto reduce el número de webhooks y facilita el procesamiento.
6. ¿Puedo recibir notificaciones de múltiples contratos en el mismo endpoint?
Sí, puedes usar la misma URL para todos tus contratos. Usa el campo codigo_unico_externo o contrato_id para identificar qué contrato cambió y enrutar la lógica de negocio correspondiente.