Introducción

Esta documentación cubre el proceso completo de firma digital de contratos:

  • Envío inicial: Enviar contratos para firma digital por primera vez
  • Reenvío: Reenviar solicitudes cuando el cliente no recibió la comunicación o necesita un recordatorio
  • Consulta de estado: Obtener información en tiempo real del proceso de firma
  • Auditoría: Datos detallados de cada firmante (IP, fecha/hora, estado)

✅ Características Principales

  • Operaciones síncronas: Respuestas inmediatas (< 2 segundos)
  • Múltiples canales: Email, SMS, o Email con OTP
  • Reenvío ilimitado: Puedes reenviar las veces que necesites
  • Trazabilidad completa: Cada operación genera un request_id único
  • Validación de permisos: Solo puedes gestionar firmas de tus contratos
  • Notificaciones automáticas: Webhooks cuando se completa la firma

Envío Inicial de Firma

El endpoint POST /firma permite enviar contratos ya creados en el sistema para firma digital del cliente. El contrato debe haber sido creado previamente mediante los endpoints de contratación.

POST /firma

ℹ️ ¿Cuándo usar este endpoint?

Utiliza este endpoint cuando:

  • Creaste un contrato con no_enviar_firma: true
  • Quieres controlar el momento exacto del envío de firma
  • Necesitas elegir el canal de envío específico (email, SMS, email_otp)

Parámetros del Request

Parámetro Tipo Requerido Descripción
contrato_id integer ✅ Sí ID del contrato. Se obtiene del campo contrato_result.id en la respuesta del endpoint de contratación
canal_envio string ✅ Sí Canal de envío: "email", "sms", o "email_otp"
direcciones_firma string ✅ Sí Email, teléfono, o ambos separados por ;
referencia_externa string ❌ No Referencia opcional para tracking interno (ej: "DEAL-12345")

Canales de Envío

📧 Email ("email")

Envío del enlace de firma por correo electrónico. Canal más común y recomendado.

  • Formato: direcciones_firma: "usuario@example.com"
  • Validación: Email en formato RFC estándar
  • Ventaja: El cliente puede firmar desde cualquier dispositivo

📱 SMS ("sms")

Envío del enlace de firma por mensaje de texto SMS.

  • Formato: direcciones_firma: "606123456" o "+34606123456"
  • Validación: 8-15 dígitos, opcional prefijo internacional +
  • Ventaja: Entrega inmediata, ideal para clientes sin email

🔐 Email con OTP ("email_otp")

Envío dual: email con enlace + SMS con código OTP de verificación.

  • Formato: direcciones_firma: "usuario@example.com;606123456"
  • Ventaja: Máxima seguridad con doble factor de autenticación

Ejemplo de Request

POST /firma
Authorization: Bearer <tu-jwt-token>
Content-Type: application/json

{
  "contrato_id": 369877,
  "canal_envio": "email",
  "direcciones_firma": "cliente@example.com",
  "referencia_externa": "ZOHO-DEAL-12345"
}

Respuesta Exitosa (200)

{
  "request_id": 12345,
  "firma_result": {
    "status": "success",
    "message": "Contrato enviado para firma digital",
    "circuito_id": "123456"
  },
  "referencia_externa": "ZOHO-DEAL-12345"
}

💡 Importante: Guarda el circuito_id

La respuesta incluye un circuito_id que necesitarás para:

  • Reenviar la solicitud de firma (endpoint POST /firma/reenviar)
  • Consultar el estado del proceso (endpoint GET /firma/{circuito_id})

Flujo de Uso Típico

  1. Paso 1: Crear el contrato con no_enviar_firma: true
  2. Paso 2: Obtener el contrato_id de la respuesta
  3. Paso 3: Enviar a firma cuando estés listo:
    POST /firma
    
    {
      "contrato_id": 369877,
      "canal_envio": "email",
      "direcciones_firma": "cliente@example.com"
    }
  4. Paso 4: Guardar el circuito_id de la respuesta para operaciones posteriores

Reenviar Solicitud de Firma

Reenvía una solicitud de firma existente al cliente. Útil para recordatorios o cuando el cliente no recibió la comunicación inicial.

POST /firma/reenviar

Parámetros del Request

Parámetro Tipo Requerido Descripción
circuito_id String ✅ Sí ID de la solicitud de firma en el sistema. Se obtiene en la respuesta de POST /firma
mode String ❌ No Modo de firma: ds (drawing signature), os (one-time certificate), ud (upload & draw). Por defecto: ds
referencia_externa String ❌ No Tu referencia interna para tracking. Máximo 100 caracteres

Ejemplo de Request

POST /firma/reenviar
Authorization: Bearer <tu-jwt-token>
Content-Type: application/json

{
  "circuito_id": "123456",
  "mode": "ds",
  "referencia_externa": "REENVIO-2024-001"
}

Respuesta Exitosa (200)

{
  "request_id": 789,
  "circuito_id": "123456",
  "estado": "reenviada",
  "mensaje": "Solicitud de firma reenviada exitosamente",
  "referencia_externa": "REENVIO-2024-001"
}

⚠️ Validación de Permisos

El sistema verifica que tu canal JWT tenga autorización sobre el contrato asociado al circuito_id. Si no tienes permisos, recibirás un error 403.

Consultar Estado de Firma

Obtiene información detallada del estado actual de una solicitud de firma, incluyendo datos de todos los firmantes y su progreso individual.

GET /firma/{circuito_id}

Parámetros del Request

Parámetro Ubicación Tipo Descripción
circuito_id Path String ID de la solicitud de firma
referencia_externa Query (opcional) String Tu referencia interna para tracking

Ejemplo de Request

GET /firma/123456?referencia_externa=CONSULTA-001
Authorization: Bearer <tu-jwt-token>

Respuesta Exitosa (200)

{
  "request_id": 790,
  "circuito_id": "123456",
  "estado": "en_proceso",
  "en_proceso": true,
  "fecha_inicio": "2024-06-09T10:30:00+02:00",
  "fecha_completado": null,
  "firmantes": [
    {
      "email": "juan.perez@example.com",
      "estado": "firmado",
      "fecha_firma": "2024-06-09T15:45:32+02:00"
    },
    {
      "email": "maria.lopez@example.com",
      "estado": "pendiente",
      "fecha_firma": null
    }
  ],
  "referencia_externa": "CONSULTA-001"
}

📊 Estados Posibles

  • pendiente: Solicitud creada pero aún no iniciada
  • en_proceso: Al menos un firmante ha iniciado el proceso
  • completado: Todos los firmantes han firmado
  • rechazado: La solicitud fue rechazada
// Ejemplo de respuesta cuando está completado
{
  "request_id": 791,
  "circuito_id": "123456",
  "estado": "completado",
  "en_proceso": false,
  "fecha_inicio": "2024-06-09T10:30:00+02:00",
  "fecha_completado": "2024-06-09T18:06:40+02:00",
  "firmantes": [
    {
      "email": "juan.perez@example.com",
      "estado": "firmado",
      "fecha_firma": "2024-06-09T15:45:32+02:00"
    },
    {
      "email": "maria.lopez@example.com",
      "estado": "firmado",
      "fecha_firma": "2024-06-09T18:06:40+02:00"
    }
  ],
  "referencia_externa": "CONSULTA-001"
}

Campos de Firmantes

Campo Descripción
email Dirección de email del firmante
estado firmado: El firmante completó su firma
pendiente: Aún no ha firmado
fecha_firma Timestamp ISO 8601 del momento exacto de la firma (null si estado = pendiente)

✅ Notificación Automática

Cuando todos los firmantes completan su firma, el sistema:

  • Cambia el contrato a Estado: 1, Subestado: 50 (Firmado)
  • Envía un webhook automático si tienes configuradas notificaciones de cambios de estado
  • Registra el evento completo en webhooks.event_evidence con event='process_completed'

Autenticación

Todos los endpoints de gestión de firma requieren autenticación JWT. Para obtener información detallada sobre cómo autenticarse, consulta la documentación de autenticación.

Gestión de Errores

Códigos de Error Comunes

Código Descripción Solución
400 Parámetro requerido faltante o inválido Verifica que circuito_id esté presente y sea válido
401 Token JWT ausente o inválido Incluye un token JWT válido en el header Authorization
403 Sin permisos sobre el circuito_id Este circuito_id pertenece a otro canal. Solo puedes gestionar firmas de tus propios contratos
404 Circuito_id no encontrado El circuito_id no existe en el sistema o nunca fue enviado a firma
500 Error interno del servidor Contacta con soporte técnico

Ejemplo de Respuesta de Error

{
  "error": "Circuito no encontrado o sin permisos",
  "request_id": 791,
  "details": {
    "circuito_id": "999999",
    "canal": "mi-canal"
  },
  "referencia_externa": "REF-123"
}

Ejemplos Completos de Integración

Ejemplo 1: Reenviar Firma (Python)

import requests

# Tu token JWT
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
headers = {
    "Authorization": f"Bearer {jwt_token}",
    "Content-Type": "application/json"
}

# Reenviar solicitud de firma
payload = {
    "circuito_id": "123456",
    "mode": "ds",
    "referencia_externa": "REENVIO-2024-001"
}

response = requests.post(
    "https://pre-webhooks.imaginaenergia.com/firma/reenviar",
    headers=headers,
    json=payload
)

if response.status_code == 200:
    result = response.json()
    print(f"✅ Recordatorio enviado. Request ID: {result['request_id']}")
else:
    print(f"❌ Error: {response.json()['error']}")

Ejemplo 2: Consultar Estado (Python)

import requests

# Tu token JWT
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
headers = {"Authorization": f"Bearer {jwt_token}"}

# Consultar estado de firma
circuito_id = "123456"
response = requests.get(
    f"https://pre-webhooks.imaginaenergia.com/firma/{circuito_id}",
    headers=headers
)

if response.status_code == 200:
    data = response.json()
    progreso = data['progreso']
    
    print(f"📊 Estado de firma del circuito {circuito_id}")
    print(f"Total firmantes: {progreso['total_firmantes']}")
    print(f"Completados: {progreso['firmantes_completados']}")
    print(f"Progreso: {progreso['porcentaje_completado']}%")
    
    print("\n👥 Firmantes:")
    for firmante in data['firmantes']:
        estado = "✅ Firmado" if firmante['completado'] else "⏳ Pendiente"
        print(f"  {firmante['nombre']}: {estado}")
        if firmante['completado']:
            print(f"    📅 Fecha: {firmante['fecha_firma']}")
            print(f"    🌐 IP: {firmante['ip_firma']}")
else:
    print(f"❌ Error: {response.json()['error']}")

Ejemplo 3: Workflow Completo (JavaScript)

// Función para reenviar firma y verificar estado
async function gestionarFirmaContrato(circuitoId, jwtToken) {
  const baseUrl = "https://pre-webhooks.imaginaenergia.com";
  const headers = {
    "Authorization": `Bearer ${jwtToken}`,
    "Content-Type": "application/json"
  };

  try {
    // 1. Reenviar solicitud de firma
    console.log("🔄 Reenviando solicitud de firma...");
    const reenvioResponse = await fetch(`${baseUrl}/firma/reenviar`, {
      method: "POST",
      headers: headers,
      body: JSON.stringify({
        circuito_id: circuitoId,
        mode: "ds",
        referencia_externa: `REENVIO-${Date.now()}`
      })
    });

    if (!reenvioResponse.ok) {
      throw new Error(`Error en reenvío: ${reenvioResponse.statusText}`);
    }

    const reenvioData = await reenvioResponse.json();
    console.log(`✅ Recordatorio enviado. Request ID: ${reenvioData.request_id}`);

    // 2. Esperar un momento y consultar estado
    await new Promise(resolve => setTimeout(resolve, 2000));

    console.log("\n🔍 Consultando estado actual...");
    const estadoResponse = await fetch(`${baseUrl}/firma/${circuitoId}`, {
      method: "GET",
      headers: headers
    });

    if (!estadoResponse.ok) {
      throw new Error(`Error consultando estado: ${estadoResponse.statusText}`);
    }

    const estadoData = await estadoResponse.json();
    const progreso = estadoData.progreso;

    console.log(`\n📊 Progreso: ${progreso.porcentaje_completado}%`);
    console.log(`   ${progreso.firmantes_completados}/${progreso.total_firmantes} firmantes han completado`);

    // 3. Mostrar estado de cada firmante
    console.log("\n👥 Estado de firmantes:");
    estadoData.firmantes.forEach(firmante => {
      const estado = firmante.completado ? "✅ Firmado" : "⏳ Pendiente";
      console.log(`   ${firmante.nombre} (${firmante.email}): ${estado}`);
      if (firmante.completado) {
        console.log(`      📅 ${firmante.fecha_firma}`);
      }
    });

    return {
      exito: true,
      progreso: progreso,
      firmantes: estadoData.firmantes
    };

  } catch (error) {
    console.error("❌ Error:", error.message);
    return {
      exito: false,
      error: error.message
    };
  }
}

// Uso
gestionarFirmaContrato("123456", "tu-jwt-token-aqui");

💡 Mejores Prácticas

  • No reenvíes demasiado frecuentemente: Espera al menos 1 hora entre reenvíos para no saturar al cliente
  • Consulta el estado antes de reenviar: Verifica si el cliente ya firmó antes de enviar recordatorios
  • Usa referencia_externa: Facilita el tracking y debugging de operaciones
  • Registra las IPs de firma: Útil para auditorías y compliance
  • Configura webhooks: En lugar de polling constante, recibe notificaciones automáticas cuando se completa la firma

Recursos Relacionados