Portal de Documentación de Logicware CRM
Guías, referencias e integración técnica para clientes y partners.
Recursos
10 seccionesGenera y renueva tokens Bearer (JWT), válidos por 1 hora, para autenticar tus llamadas a la API.
Consulta stock de unidades, gestiona leads, crea citas y registra actividades en tiempo real.
Recibe eventos de negocio en tiempo real vía HTTP POST con application/json.
Guías funcionales y técnicas para tu equipo de integración y soporte.
Activa y mantén la conexión de WhatsApp con el CRM Logicware.
Bibliotecas y ejemplos de implementación en Node.js, C# y Python.
Widget conversacional para tu web que responde consultas y capta leads en tu CRM.
Todos los accesos por HTTPS, con validación HMAC en tiempo constante.
Canales de contacto y SLA de soporte técnico.
Historial de cambios y nuevas versiones de la documentación.
Introducción
Bienvenido al portal de documentación de Logicware CRM. Aquí encontrarás especificaciones de integración, manuales funcionales y recursos para desarrolladores. Usa el menú lateral para navegar por las diferentes secciones disponibles.
Webhooks
Resumen
Nuestros webhooks notifican eventos de negocio en tiempo real a tu endpoint mediante HTTP POST con application/json. Implementan reintentos con exponential backoff y envían un identificador único por mensaje para idempotencia.
Seguridad
Si configuras un Secret, cada petición incluye X-Webhook-Signature como sha256=<hash_hex>, calculada con HMAC‑SHA256 sobre el cuerpo crudo del payload. Valida en tu servidor comparando la firma en tiempo constante y usa siempre HTTPS.
Estructura del payload
{
"messageId": "uuid",
"eventType": "proforma.created",
"eventTimestamp": "ISO8601",
"data": { /* campos específicos del evento */ },
"sourceId": "id interno",
"correlationId": "cadena única",
"tenantId": "id de tu cuenta (omitido si es null)"
}Campos del Payload
| Campo | Tipo | Descripción |
|---|---|---|
messageId | string (UUID) | Identificador único del mensaje para idempotencia |
eventType | string | Tipo de evento (ver lista de eventos disponibles) |
eventTimestamp | datetime (ISO 8601) | Fecha y hora en que ocurrió el evento |
data | object | Datos específicos del evento (varía según eventType) |
sourceId | string | ID interno de la entidad que generó el evento |
correlationId | string | Identificador único para rastreo y debugging |
tenantId | string | ID de tu cuenta/tenant en Logicware (el campo se omite si es null) |
Eventos disponibles
Actualmente se soportan 12 tipos de eventos organizados por categoría:
📋 Leads
lead.created- Lead creadolead.updated- Lead actualizado
💰 Proceso de Ventas
proforma.created- Proforma creadasales.process.started- Proceso de venta iniciadosales.process.completed- Proceso de venta completado
🔒 Proceso de Separación
separation.process.started- Separación iniciadaseparation.process.completed- Separación completada
↩️ Proceso de Devolución
refund.process.started- Devolución iniciadarefund.process.completed- Devolución completada
💳 Cronogramas
schedule.created- Cronograma creado
🏗️ Unidades
unit.created- Unidad creadaunit.updated- Unidad actualizada
El catálogo puede ampliarse en futuras versiones. Eventos desconocidos: registrar o ignorar de forma segura.
Detalles de Eventos
| Tipo de Evento | Nombre | Descripción |
|---|---|---|
lead.created | Lead Creado | Se dispara cuando se crea un nuevo lead |
lead.updated | Lead Actualizado | Se dispara cuando se actualiza un lead existente |
proforma.created | Proforma Creada | Se dispara cuando se crea una nueva proforma |
sales.process.started | Proceso de Venta Iniciado | Se dispara cuando se inicia un proceso de venta |
sales.process.completed | Proceso de Venta Completado | Se dispara cuando se completa un proceso de venta |
separation.process.started | Proceso de Separación Iniciado | Se dispara cuando se inicia un proceso de separación |
separation.process.completed | Proceso de Separación Completado | Se dispara cuando se completa un proceso de separación |
refund.process.started | Proceso de Devolución Iniciado | Se dispara cuando se inicia un proceso de devolución |
refund.process.completed | Proceso de Devolución Completado | Se dispara cuando se completa un proceso de devolución |
schedule.created | Cronograma Creado | Se dispara cuando se crea un nuevo cronograma |
unit.created | Unidad Creada | Se dispara cuando se crea una nueva unidad |
unit.updated | Unidad Actualizada | Se dispara cuando se actualiza una unidad |
Ejemplo (proforma.created)
{
"messageId":"a2e4bf25-c970-4d69-9336-9b6d09b89459",
"eventType":"proforma.created",
"eventTimestamp":"2025-09-02T23:46:11.634-05:00",
"data": {
"ord_correlative":"202509-000000274",
"ord_total":493.08,
"client":{
"type_document":"DNI",
"document":"12345678",
"full_name":"JUAN PEREZ PEREZ"
},
"units":[
{ "unit_number":"M-01", "sub_total":493.08 }
]
},
"sourceId":"5376",
"correlationId":"proforma.created-5376-..."
}Nota: el objeto data mostrado arriba es un subconjunto simplificado. Para eventos de proforma/venta/separación/devolución, el payload real incluye además datos del vendedor (seller), marketing (marketing), cónyuge (client.spouse) y, en devoluciones, el detalle del extorno (refund), entre otros campos. La estructura completa varía según eventType.
Más detalles, pruebas y ejemplos por lenguaje en la guía completa de Webhooks.
¿Dudas? Nuestro equipo puede ayudarte con la validación de firmas y reintentos.
Recomendaciones de Implementación
✅ Mejores Prácticas
- Idempotencia: Usa
messageIdpara evitar procesar el mismo evento múltiples veces - Validación: Siempre valida la firma
X-Webhook-Signatureantes de procesar - Respuesta rápida: Responde con HTTP 200 lo antes posible y procesa el evento de forma asíncrona
- Eventos desconocidos: Ignora eventos que no reconozcas para mantener compatibilidad futura
- Logs: Registra
correlationIdpara facilitar el rastreo y debugging
⚠️ Errores Comunes a Evitar
- Procesamiento sincrónico: No bloquees la respuesta HTTP con procesamiento largo
- Sin validación de firma: Siempre valida para evitar webhooks maliciosos
- Ignorar messageId: Puede causar procesamiento duplicado
- Timeouts largos: Responde en menos de 5 segundos
- No manejar reintentos: El sistema reintentará si fallas, prepara tu lógica
API de Proveedores v2.1
Introducción
La API de Proveedores de Logicware permite a los proveedores autorizados consultar stock de unidades, gestionar leads, crear citas y registrar actividades en tiempo real.
🔐 Seguro
Autenticación robusta con tokens JWT y API Keys. Todas las comunicaciones sobre HTTPS.
⚡ Rápido
Rate limiting por tier de proveedor (100-500 req/min). Respuestas optimizadas y reintentos automáticos.
📚 Documentado
Ejemplos completos en cURL, JavaScript, C# y Python con casos de uso reales.
Requisitos Previos
Credenciales proporcionadas
| Credencial | Descripción | Ejemplo |
|---|---|---|
X-API-Key | Clave única de identificación | pk_live_abc123... |
X-Subdomain | Subdominio asignado | proveedor-demo |
projectCode | Código de proyecto(s) | PROJ-2025-001 |
Headers Comunes
Todos los endpoints requieren los siguientes headers:
X-Request-ID es fundamental para trazabilidad y soporte técnico. Mantenga sus credenciales seguras y nunca las exponga en código público.
X-API-Key: {su-api-key} (solo al generar token)
X-Subdomain: {su-subdomain}
Authorization: Bearer {accessToken}
Accept: application/json
X-Request-ID: {UUID-v4-único} // Recomendado
Content-Type: application/json // Solo en POSTAutenticación
/auth/external/token Flujo de autenticación
- Generar token usando su API Key
- Incluir el token en el header
Authorization - Renovar el token antes de su expiración (1 hora)
Nota: La duración del token y el límite de solicitudes por minuto no son valores fijos de la plataforma — dependen de la configuración del sistema y del tier asignado a su cuenta de proveedor. El ejemplo de respuesta muestra los valores típicos de un proveedor standard.
POST /auth/external/token
Headers:
X-API-Key: {su-api-key}
X-Subdomain: {su-subdomain}
X-Request-ID: {id-opcional-para-trazabilidad}
Accept: application/json
Body: (vacío){
"succeeded": true,
"message": "Token generated successfully",
"data": {
"accessToken": "eyJhbGciOiJIUzI1NiIs...",
"tokenType": "Bearer",
"expiresIn": 3600,
"expiresAt": "2025-09-25T02:39:17.0346763Z",
"provider": {
"id": "PROV002",
"name": "LOGICWARE S.A.C",
"accessType": "standard"
},
"rateLimits": {
"requestsPerMinute": 200
}
},
"statusCode": 200
}Endpoints Disponibles
/external/units/stock?projectCode={code}&stageId={id} Stock de Unidades
Obtiene el inventario disponible de unidades para un proyecto y etapa específicos, incluyendo información detallada de dimensiones, precios y estado de disponibilidad.
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
projectCode | string | Sí | Código único del proyecto inmobiliario |
stageId | integer | Sí | ID de la etapa del proyecto |
status | integer | No | ID del estado comercial para filtrar las unidades (opcional) |
Nota: La respuesta incluye un array de unidades (properties) y un resumen (summary) con el total de unidades devueltas. Solo se devuelven unidades de la etapa especificada.
Campos de la Unidad
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único de la unidad |
stageId | integer | ID de la etapa a la que pertenece la unidad |
stageName | string | Nombre de la etapa del proyecto |
blockName | string | Nombre de la manzana o bloque |
unitTypeName | string | Nombre del tipo de unidad (ej: Lote, Departamento) |
code | string | Código identificador del lote (ej: E2-01) |
description | string | Descripción de la unidad |
remarks | string | Observaciones o características especiales |
deliveryDate | datetime | Fecha estimada de entrega (puede ser null) |
areaSqm | decimal | Área total en metros cuadrados |
dimensions | object | Medidas de los lados de la unidad |
dimensions.front | decimal | Medida del frente en metros |
dimensions.right | decimal | Medida del lado derecho en metros |
dimensions.left | decimal | Medida del lado izquierdo en metros |
dimensions.back | decimal | Medida del fondo en metros |
statuses | object | Detalle del estado comercial actual de la unidad |
statuses.id | integer | ID del estado comercial |
statuses.description | string | Descripción del estado comercial (ej: Disponible, Reservado) |
pricePerSqm | decimal | Precio por metro cuadrado |
totalPrice | decimal | Precio total de la unidad |
currency | string | Moneda del precio (PEN, USD) |
status | string | Nombre del estado actual de la unidad (equivalente a statuses.description) |
createdAt | datetime | Fecha de creación del registro |
updatedAt | datetime | Fecha de última actualización del registro (puede ser null) |
Estados de Unidad
- Disponible: Unidad lista para venta, sin restricciones
- Reservado: Unidad con reserva activa de un cliente
- Vendido: Unidad ya vendida, no disponible
- Bloqueado: Unidad temporalmente no disponible para venta
Campos del Resumen (Summary)
| Campo | Tipo | Descripción |
|---|---|---|
total | integer | Total de unidades devueltas en la respuesta |
GET /external/units/stock?projectCode={code}&stageId={id}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Unit stock retrieved successfully",
"data": {
"properties": [
{
"id": 1223,
"stageId": 2,
"stageName": "ETAPA 2",
"blockName": "MZ E2",
"unitTypeName": "Lote",
"code": "E2-01",
"description": "",
"remarks": "",
"orientation": null,
"isCorner": false,
"deliveryDate": "2026-03-15T00:00:00",
"areaSqm": 108.00,
"dimensions": {
"front": 6.00,
"right": 18.00,
"left": 18.00,
"back": 6.00
},
"leaflet": {
"id": 45,
"coordinates": "[[4494.956250190735,4861.089111394753],[4233.718328306151,4727.418198428964],[4427.331251144409,4381.952347764205],[4493.956251144409,4415.326717569711],[4535.581251144409,4435.7048303485735],[4579.956251144409,4458.9583823539015],[4625.456251144409,4482.455116800616],[4657.706251144409,4500.957943127436],[4664.706251144409,4505.330384155856],[4671.581251144409,4510.956243511984],[4680.581251144409,4519.957618481788],[4689.331251144409,4531.709413581255],[4696.331251144409,4547.086762488003],[4699.206251144409,4558.588519393864],[4700.331251144409,4566.089665202035],[4699.706251144409,4581.3419950119805],[4698.831251144409,4580.46686133436],[4697.956251144409,4591.093484562602],[4693.456251144409,4603.34535604928],[4680.206251144409,4623.22339244093],[4639.331251144409,4672.980992968459]]",
"positionNumber": "E2-01"
},
"statuses": {
"id": 1,
"description": "Disponible",
"background": "#22C55E"
},
"pricePerSqm": 520.93,
"totalPrice": 56260.58,
"currency": "PEN",
"status": "Disponible",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": null
},
{
"id": 1224,
"stageId": 2,
"stageName": "ETAPA 2",
"blockName": "MZ E2",
"unitTypeName": "Lote",
"code": "E2-02",
"description": "Lote esquinero con vista panorámica",
"remarks": "Ubicación privilegiada, frente a parque",
"orientation": "Norte",
"isCorner": true,
"deliveryDate": "2026-03-15T00:00:00",
"areaSqm": 120.00,
"dimensions": {
"front": 8.00,
"right": 15.00,
"left": 15.00,
"back": 8.00
},
"leaflet": {
"id": 46,
"coordinates": "[[4210.456250190735,4283.321771497776],[4044.0424347401104,4641.575938349752],[3837.956251144409,4558.947344401862],[3989.3045545454693,4193.621062001607]]",
"positionNumber": "E2-02"
},
"statuses": {
"id": 2,
"description": "Reservado",
"background": "#F59E0B"
},
"pricePerSqm": 520.83,
"totalPrice": 62500.00,
"currency": "PEN",
"status": "Reservado",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": "2025-06-11T16:05:00"
}
],
"summary": {
"total": 2
}
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Parámetro projectCode faltante o stageId inválido (menor o igual a 0) |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando una etapa no tiene unidades configuradas, la respuesta sigue siendo exitosa (200), pero el array properties está vacío, summary.total es 0 y el mensaje cambia a "No stock found for the specified project and stage". Este endpoint nunca responde con 404.
{
"succeeded": false,
"message": "projectCode is required",
"data": null,
"statusCode": 400
}{
"succeeded": false,
"message": "stageId must be greater than 0",
"data": null,
"statusCode": 400
}{
"succeeded": true,
"message": "No stock found for the specified project and stage",
"data": {
"properties": [],
"summary": {
"total": 0
}
},
"statusCode": 200
}/external/units/{unitId}/files Archivos de un Inmueble
Obtiene los archivos (planos, imágenes u otros documentos) vinculados a una unidad específica, usando el id devuelto por los endpoints de stock.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
unitId | integer | Sí | ID de la unidad (campo id devuelto por Stock, Detalle de Unidad o Comparar Unidades) |
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
expirationMinutes | integer | No | Vigencia de las URLs firmadas en minutos (por defecto: 60) |
Estructura de Archivo
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único del archivo |
fileName | string | Nombre del archivo adjunto |
fileSize | integer | Tamaño del archivo en bytes |
url | string | URL temporal firmada para descargar el archivo |
createdAt | datetime | Fecha en que se adjuntó el archivo |
GET /external/units/1245/files?expirationMinutes=120
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Unit files retrieved successfully",
"data": [
{
"id": 301,
"fileName": "plano-b15.pdf",
"fileSize": 482913,
"url": "https://cdn.logicwareperu.com/apiFilesCrm/units/plano-b15.pdf?sig=...",
"createdAt": "2025-06-10T09:20:00"
}
],
"statusCode": 200
}Códigos de Respuesta
| Código | Mensaje | Descripción |
|---|---|---|
200 | OK | Archivos obtenidos (o sin archivos vinculados, ver nota) |
404 | Not Found | La unidad especificada no existe |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando la unidad existe pero no tiene archivos vinculados, la respuesta es exitosa (200) con data: [].
{
"succeeded": false,
"message": "Unit with id 1245 not found",
"data": null,
"statusCode": 404
}{
"succeeded": true,
"message": "No files found for the specified unit",
"data": [],
"statusCode": 200
}/external/projects Proyectos
Obtiene todos los proyectos inmobiliarios disponibles para el tenant autenticado.
projectCode obtenido es necesario para consultar etapas, unidades y disponibilidad.
Campos del Proyecto
| Campo | Tipo | Descripción |
|---|---|---|
projectId | integer | ID único del proyecto |
projectCode | string | Código único del proyecto, usado como parámetro en otros endpoints |
projectName | string | Nombre descriptivo del proyecto inmobiliario |
status | string | Estado actual del proyecto (ej. Activo, Inactivo) |
createdAt | datetime | Fecha de creación del proyecto |
updatedAt | datetime | null | Fecha de última actualización, null si no ha sido modificado |
GET /external/projects
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Projects retrieved successfully",
"data": [
{
"projectId": 1,
"projectCode": "31125",
"projectName": "VILLA ECO-SOSTENIBLE",
"status": "Activo",
"createdAt": "2025-11-03T23:32:57.41",
"updatedAt": null
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
403 | Forbidden | El tenant no tiene permisos para acceder a este recurso |
Nota: Cuando el tenant no tiene proyectos configurados, la respuesta es exitosa (200) pero el array data está vacío.
{
"succeeded": true,
"message": "No projects found.",
"data": [],
"statusCode": 200
}/external/stages?projectCode={code} Etapas de Proyecto
Obtiene todas las etapas disponibles para un proyecto inmobiliario específico, incluyendo fechas de entrega y configuración.
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
projectCode | string | Sí | Código único del proyecto inmobiliario |
Nota: La respuesta incluye todas las etapas configuradas para el proyecto especificado, ordenadas por stageId. El stageId es necesario para consultar stock de unidades y bloques.
Campos de la Etapa
| Campo | Tipo | Descripción |
|---|---|---|
stageId | integer | ID único de la etapa |
stageName | string | Nombre descriptivo de la etapa |
projectCode | string | Código del proyecto al que pertenece |
deliveryDate | datetime | Fecha estimada de entrega de la etapa |
createdAt | datetime | Fecha de creación del registro |
updatedAt | datetime | Fecha de última actualización del registro (puede ser null) |
GET /external/stages?projectCode={code}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Stages retrieved successfully",
"data": [
{
"stageId": 1,
"stageName": "Etapa 1",
"projectCode": "LOGICWARE",
"deliveryDate": "2025-06-30T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": null
},
{
"stageId": 2,
"stageName": "Etapa 2",
"projectCode": "LOGICWARE",
"deliveryDate": "2026-03-15T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": "2025-05-02T14:40:00"
},
{
"stageId": 3,
"stageName": "Etapa 3",
"projectCode": "LOGICWARE",
"deliveryDate": "2026-12-20T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": null
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Parámetro projectCode faltante o inválido |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando un proyecto no tiene etapas configuradas, la respuesta sigue siendo exitosa (200), pero el array data está vacío y el mensaje cambia a "No stages found for the specified project". Este endpoint nunca responde con 404.
{
"succeeded": false,
"message": "projectCode is required",
"data": null,
"statusCode": 400
}{
"succeeded": true,
"message": "No stages found for the specified project",
"data": [],
"statusCode": 200
}/external/stages/{projectId}/{stageId}/default/url URL Firmada del Plano de Etapa
Genera una URL temporal firmada para acceder a la imagen del plano por defecto de una etapa, útil para renderizar el mapa de la etapa sin exponer el almacenamiento de archivos directamente.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
projectId | integer | Sí | ID único del proyecto (no el projectCode) |
stageId | integer | Sí | ID único de la etapa |
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
expirationMinutes | integer | No | Vigencia de la URL firmada en minutos (por defecto: 60) |
Estructura de Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
signedUrl | string | URL temporal firmada del plano de la etapa |
expiresAtUtc | datetime | Fecha y hora (UTC) en que expira la URL |
GET /external/stages/12/1/default/url?expirationMinutes=120
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Default stage plan signed URL generated successfully",
"data": {
"signedUrl": "https://cdn.logicwareperu.com/apiFilesCrm/stages/logicware-etapa1.png?sig=...",
"expiresAtUtc": "2025-10-15T14:30:00Z"
},
"statusCode": 200
}Códigos de Respuesta
| Código | Mensaje | Descripción |
|---|---|---|
200 | OK | URL generada exitosamente |
404 | Not Found | No existe un plano por defecto para el projectId/stageId indicados |
401 | Unauthorized | Token de autenticación inválido o expirado |
{
"succeeded": false,
"message": "Default stage plan not found for projectId 12 and stageId 1",
"data": null,
"statusCode": 404
}/external/blocks?projectCode={code} Bloques de Proyecto
Obtiene todos los bloques (manzanas) disponibles para un proyecto inmobiliario específico, útil para organizar y consultar unidades por sectores.
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
projectCode | string | Sí | Código único del proyecto inmobiliario |
Nota: La respuesta incluye todos los bloques configurados para el proyecto especificado, ordenados por blockId.
Campos del Bloque
| Campo | Tipo | Descripción |
|---|---|---|
blockId | integer | ID único del bloque/manzana |
blockName | string | Nombre identificador del bloque (ej: MZ A, MZ B) |
projectCode | string | Código del proyecto al que pertenece |
deliveryDate | datetime | Fecha estimada de entrega del bloque |
createdAt | datetime | Fecha de creación del registro |
updatedAt | datetime | Fecha de última actualización del registro (puede ser null) |
GET /external/blocks?projectCode={code}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Blocks retrieved successfully",
"data": [
{
"blockId": 101,
"blockName": "MZ A",
"projectCode": "LOGICWARE",
"deliveryDate": "2025-08-15T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": null
},
{
"blockId": 102,
"blockName": "MZ B",
"projectCode": "LOGICWARE",
"deliveryDate": "2025-08-15T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": "2025-04-18T11:22:00"
},
{
"blockId": 103,
"blockName": "MZ C",
"projectCode": "LOGICWARE",
"deliveryDate": "2026-02-28T00:00:00",
"createdAt": "2025-01-10T09:15:00",
"updatedAt": null
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Parámetro projectCode faltante o inválido |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando un proyecto no tiene bloques configurados, la respuesta sigue siendo exitosa (200), pero el array data está vacío y el mensaje cambia a "No blocks found for the specified project". Este endpoint nunca responde con 404.
{
"succeeded": false,
"message": "projectCode is required",
"data": null,
"statusCode": 400
}{
"succeeded": true,
"message": "No blocks found for the specified project",
"data": [],
"statusCode": 200
}/external/leads/{leadId} Información de Lead
Obtiene información completa de un lead específico, incluyendo datos personales, vendedor asignado, proyecto de interés, canales de adquisición y estado actual.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
leadId | integer | Sí | ID único del lead en el sistema |
Nota: La respuesta incluye información completa del lead, incluyendo datos de contacto, proyecto de interés, tracking UTM y vendedor asignado. Algunos campos pueden ser null o estar vacíos si no se han configurado.
Campos del Lead
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único del lead |
documentNumber | string | Número de documento (DNI/RUC) |
firstName | string | Nombre del lead |
paternalSurname | string | Apellido paterno |
maternalSurname | string | Apellido materno |
fullName | string | Nombre completo concatenado |
phone | string | Número de teléfono/celular |
email | string | Correo electrónico |
createdAt | datetime | Fecha y hora de creación del lead |
firstEntryDate | datetime | Fecha de primer contacto |
createdMonth | string | Mes de creación en texto inglés |
status | string | Estado del lead (Activo, Cerrado, etc.) |
segmentName | string | Segmento o etapa del funnel (Nuevos, Calificados, etc.) |
projectName | string | Nombre del proyecto de interés |
stageName | string | Etapa del proyecto de interés |
quotationPortal | string | Portal/sistema de origen del lead |
acquisitionChannel | string | Canal de adquisición del lead |
entryChannel | string | Canal de entrada (WhatsApp, Web, Teléfono, etc.) |
utmCampaign | string | Campaña de marketing (parámetro UTM) |
utmContent | string | Contenido específico (parámetro UTM) |
utmMedium | string | Medio de marketing (parámetro UTM) |
utmSource | string | Fuente de tráfico (parámetro UTM) |
utmTerm | string | Término de búsqueda (parámetro UTM) |
Campos del Vendedor
| Campo | Tipo | Descripción |
|---|---|---|
seller.firstName | string | Nombre del vendedor asignado |
seller.paternalSurname | string | Apellido paterno del vendedor |
seller.maternalSurname | string | Apellido materno del vendedor |
seller.fullName | string | Nombre completo del vendedor |
seller.email | string | Correo electrónico del vendedor |
seller.phone | string | Teléfono del vendedor |
Estados de Lead
- Activo: Lead activo en proceso de seguimiento
- Cerrado: Lead cerrado por venta o sin conversión
- Cerrado por duplicado: Lead duplicado, cerrado automáticamente
GET /external/leads/{leadId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Lead retrieved successfully",
"data": {
"id": 12345,
"documentNumber": "72845936",
"firstName": "Carlos",
"paternalSurname": "Mendoza",
"maternalSurname": "Rivera",
"fullName": "Carlos Mendoza Rivera",
"phone": "+51987654321",
"email": "carlos.mendoza@example.com",
"createdAt": "2025-03-15T10:30:00",
"firstEntryDate": "2025-03-15T10:30:00",
"createdMonth": "March",
"status": "Activo",
"segmentName": "Nuevos",
"projectName": "Residencial Vista Hermosa",
"stageName": "Etapa 2",
"quotationPortal": "WHATSAPP MANTRA",
"acquisitionChannel": "Publicidad Digital",
"entryChannel": "WhatsApp",
"utmCampaign": "verano2025",
"utmContent": "lotes-norte",
"utmMedium": "social",
"utmSource": "facebook",
"utmTerm": "wpp",
"seller": {
"firstName": "Andrea",
"paternalSurname": "Vargas",
"maternalSurname": "Mendoza",
"fullName": "Andrea Vargas Mendoza",
"email": "andrea.vargas@inmobiliaria.com",
"phone": "+51960859432"
}
},
"statusCode": 200
}/external/leads/create Crear Lead
Crea un nuevo lead en el sistema con información del prospecto, origen de marketing y preferencias de contacto.
Comportamiento con duplicados: Si ya existe un lead con el mismo teléfono o email, el sistema crea un nuevo lead pero lo marca como "Cerrado por duplicado" y lo asigna al vendedor del lead original activo. La respuesta es exitosa (201 Created) e incluye tanto el ID del nuevo lead (leadId) como el ID del lead original (parentId).
Campos requeridos:
portalCode- Código del portal origen (asignado por Logicware)projectCode- Código del proyecto (asignado por Logicware)firstName- Nombres del leademailOphoneNumber- Al menos uno de los dos debe ser proporcionado
Los demás campos son opcionales pero se recomienda enviar la mayor cantidad de información posible para mejorar la calidad del lead.
Campos de la Solicitud
Información Básica (Requeridos)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
portalCode | string | Sí | Código del portal o fuente de origen del lead (asignado por Logicware) |
projectCode | string | Sí | Código del proyecto inmobiliario de interés (asignado por Logicware) |
documentType | integer | No | Tipo de documento (ver tabla de tipos disponibles) |
firstName | string | Sí | Nombres del lead |
email | string | Condicional* | Correo electrónico (requerido si no se envía phoneNumber) |
phoneNumber | string | Condicional* | Número de teléfono con código de país (requerido si no se envía email) |
* Al menos uno de los dos campos (email o phoneNumber) debe ser proporcionado. Se recomienda enviar ambos cuando estén disponibles.
Información Adicional (Opcionales)
| Campo | Tipo | Descripción |
|---|---|---|
documentNumber | string | Número de documento de identidad |
paternalLastname | string | Apellido paterno |
maternalLastname | string | Apellido materno |
comments | string | Comentarios o notas adicionales |
availabilityType | string | Disponibilidad (inmediate, 1week, 1month, 3months, 6months) |
preferredContactTime | string | Horario preferido (morning, afternoon, evening) |
marketingConsent | string | Consentimiento de marketing (si/no) |
interestedProperties | string | Propiedades o tipos de unidad de interés |
budgetRange | string | Rango de presupuesto |
Parámetros UTM (Tracking)
| Campo | Tipo | Descripción |
|---|---|---|
utmSource | string | Fuente del tráfico (google, facebook, bot_logicware) |
utmMedium | string | Medio de marketing (cpc, email, chat, social) |
utmCampaign | string | Nombre de la campaña |
utmContent | string | Contenido específico del anuncio |
utmTerm | string | Término de búsqueda |
Asignación de Vendedor y Campos Personalizados
| Campo | Tipo | Descripción |
|---|---|---|
codSeller | integer | Código/ID del vendedor para asignación directa (0 = asignación automática) |
emailSeller | string | Email del vendedor para asignación directa (vacío = asignación automática) |
udfField1-5 | string | Campos personalizados definidos por el usuario |
- Con codSeller o emailSeller: El lead se asigna directamente al vendedor especificado
- Sin especificar vendedor: El sistema asigna automáticamente según las reglas de distribución configuradas (round-robin)
- Lead duplicado: Se asigna al vendedor del lead original activo, ignorando codSeller/emailSeller
Tipos de Documento Válidos
| ID | Tipo de Documento | Descripción |
|---|---|---|
1 | DNI | Documento Nacional de Identidad (Perú) |
2 | RUC | Registro Único de Contribuyentes (Perú - Empresas) |
3 | CARNÉ DE EXTRANJERÍA | Documento para extranjeros residentes en Perú |
4 | CARNET DIPLOMÁTICO | Documento para personal diplomático |
5 | PASAPORTE | Documento de viaje internacional |
6 | INDOCUMENTADO | Sin documento de identidad |
Códigos de Error
| Código | Descripción |
|---|---|
400 | Faltan campos requeridos o datos inválidos |
401 | Unauthorized - Token inválido o expirado |
POST /external/leads/create
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json{
"portalCode": "LOGICWAREBOT",
"projectCode": "ALAMEDA2",
"documentType": 1,
"documentNumber": "12345678",
"firstName": "Carlos",
"paternalLastname": "Rodriguez",
"maternalLastname": "Lopez",
"email": "carlos.rodriguez@email.com",
"phoneNumber": "+51987654321",
"comments": "Interesado en departamentos de 3 dormitorios",
"availabilityType": "1month",
"preferredContactTime": "afternoon",
"marketingConsent": "si",
"interestedProperties": "Departamentos 3 dorm",
"budgetRange": "150000-200000",
"utmCampaign": "verano2025",
"utmContent": "chat",
"utmMedium": "social",
"utmSource": "facebook",
"utmTerm": "departamentos lima",
"codSeller": 0,
"emailSeller": "",
"udfField1": null,
"udfField2": null,
"udfField3": null,
"udfField4": null,
"udfField5": null
}{
"succeeded": true,
"message": "Lead created successfully",
"data": {
"leadId": 1523,
"parentId": null,
"assignedTo": "Maria Fernandez Torres",
"createdAt": "2025-10-27T14:30:00"
},
"statusCode": 201
}{
"succeeded": true,
"message": "Lead created successfully",
"data": {
"leadId": 1524,
"parentId": 1523,
"assignedTo": "Maria Fernandez Torres",
"createdAt": "2025-10-27T15:05:00"
},
"statusCode": 201
}{
"succeeded": false,
"message": "Validation failed",
"errors": [
"Full name is required",
"Either Email or PhoneNumber must be provided."
],
"data": null,
"statusCode": 400
}Estructura de la Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead creado (siempre se crea un nuevo registro) |
parentId | integer/null | ID del lead original activo cuando este lead se crea como duplicado. null si no es un duplicado |
assignedTo | string | Nombre completo del vendedor asignado |
createdAt | datetime | Fecha y hora de creación del lead |
Importante: El endpoint siempre retorna código 201 y crea un nuevo lead. Si es duplicado, el nuevo lead se crea con estado "Cerrado por duplicado", se asigna al vendedor del lead original y parentId incluye el ID de ese lead original.
Códigos de Error Comunes (Consulta)
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | Lead no encontrado con el ID especificado |
{
"succeeded": false,
"message": "Lead not found",
"data": null,
"statusCode": 404
}/external/leads/{leadId}/tags Consultar Etiquetas de un Lead
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead del cual consultar etiquetas |
Nota: La respuesta incluye información completa del lead y todas sus etiquetas asignadas. El catálogo de etiquetas disponibles para asignar se consulta en Etiquetas Disponibles.
Campos de la Etiqueta
| Campo | Tipo | Descripción |
|---|---|---|
tagId | integer | ID único de la etiqueta |
tagName | string | Nombre de la etiqueta |
createdAt | datetime | Fecha y hora de asignación de la etiqueta al lead |
GET /external/leads/{leadId}/tags
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Tags retrieved successfully",
"data": {
"lead": {
"id": 73,
"fullName": "MARISA ARAUJO",
"phone": "+51998877661",
"email": "51998877661@web.whatsapp.net"
},
"tags": [
{
"tagId": 1,
"tagName": "RECONTACTO",
"createdAt": "2025-12-03T10:19:09.057"
}
]
},
"statusCode": 200
}/external/leads/{leadId}/tags Asignar Etiqueta a Lead
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead al cual asignar la etiqueta |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
tagId | integer | Sí | ID de la etiqueta a asignar (debe existir en el catálogo) |
Importante: El tagId enviado debe corresponder a una etiqueta existente en el catálogo (ver Etiquetas Disponibles).
POST /external/leads/{leadId}/tags
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"tagId": 2
}{
"succeeded": true,
"message": "Tag created successfully",
"data": {
"tagId": 2,
"tagName": "ETIQUETA 1",
"createdAt": "2025-12-10T14:15:58.487"
},
"statusCode": 201
}Códigos de Error Comunes (Etiquetas de Lead)
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Not Found | Errores de validación en los datos enviados | Lead o etiqueta no encontrada |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando un lead no tiene etiquetas asignadas, la respuesta es exitosa (200) pero el array tags está vacío.
{
"succeeded": false,
"message": "The Lead does not exist.",
"data": null,
"statusCode": 400
}{
"succeeded": false,
"message": "The tag does not exist.",
"data": null,
"statusCode": 400
}{
"succeeded": true,
"message": "No tags found for the specified lead",
"data": {
"lead": {
"id": 73,
"fullName": "MARISA ARAUJO",
"phone": "+51998877661",
"email": "51998877661@web.whatsapp.net"
},
"tags": []
},
"statusCode": 200
}/external/leads/{leadId}/activities Consultar Actividades de un Lead
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead del cual consultar actividades |
Nota: La respuesta incluye información completa del lead y un array de todas sus actividades, ordenadas por fecha de creación descendente. Cada actividad incluye los detalles del vendedor asignado.
Campos de la Actividad
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único de la actividad |
activityType | string | Tipo de actividad (Llamada, Email, WhatsApp, etc.) |
description | string | Descripción breve de la actividad |
notes | string | Notas detalladas (puede contener HTML) |
scheduledAt | datetime | Fecha y hora programada |
status | string | Estado actual: Pendiente, Completada, Vencida |
completedComments | string/null | Comentarios al completar la actividad |
completedAt | datetime/null | Fecha de finalización de la actividad |
userCompleted | string/null | Nombre del usuario que completó la actividad |
createdAt | datetime | Fecha de creación de la actividad |
updatedAt | datetime/null | Fecha de última actualización |
seller | object | Información completa del vendedor asignado |
{
"succeeded": true,
"message": "Activities retrieved successfully",
"data": {
"lead": {
"id": 19902,
"fullName": "Carlos Ramírez Torres",
"phone": "+51928519178",
"email": "carlos.ramirez@example.com"
},
"activities": [
{
"id": 40278,
"activityType": "Llamada",
"description": "Llamada",
"notes": "<p>INTERACCIÓN 1 Y SEGUIMIENTO</p>",
"scheduledAt": "2025-09-19T15:00:00",
"status": "Vencida",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"createdAt": "2025-09-18T21:15:37.583",
"updatedAt": null,
"seller": {
"firstName": "Ana",
"paternalSurname": "Martínez",
"maternalSurname": "Silva",
"fullName": "Ana Martínez Silva",
"email": "ana.martinez@inmobiliaria.com",
"phone": "+51924212619"
}
},
{
"id": 39639,
"activityType": "Llamada",
"description": "Llamada",
"notes": "Se ha creado la actividad Llamada con fecha de vencimiento 18/09/2025 a las 13:14",
"scheduledAt": "2025-09-18T13:14:14.467",
"status": "Completada",
"completedComments": "Contacto establecido exitosamente",
"completedAt": "2025-09-18T21:15:37.38",
"userCompleted": "Ana Martínez Silva",
"createdAt": "2025-09-18T11:14:14.467",
"updatedAt": "2025-09-18T21:15:37.38",
"seller": {
"firstName": "Ana",
"paternalSurname": "Martínez",
"maternalSurname": "Silva",
"fullName": "Ana Martínez Silva",
"email": "ana.martinez@inmobiliaria.com",
"phone": "+51924212619"
}
}
]
},
"statusCode": 200
}/external/leads/{leadId}/activities Gestión de Actividades
Permite crear y gestionar actividades de seguimiento para leads. El sistema automáticamente asigna la actividad al vendedor del lead.
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
activityType | integer | Sí | ID del tipo de actividad (ver endpoint de tipos) |
levelInterest | integer | Sí | Nivel de interés del lead (ver endpoint de nivel de interes) |
description | string | Sí | Descripción de la actividad |
scheduledAt | datetime | Sí | Fecha y hora programada en hora local (UTC-5, Lima-Perú). |
notes | string | No | Notas adicionales sobre la actividad |
Estados de Actividad
- Pendiente: Actividad programada, aún no completada
- Completada: Actividad realizada exitosamente
- Vencida: Actividad no completada en la fecha programada
POST /external/leads/{leadId}/activities
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"activityType": 1,
"levelInterest": 2,
"description": "Llamar al cliente para confirmar interés en departamentos",
"scheduledAt": "2025-10-20T15:00:00",
"notes": "Cliente mencionó preferencia por zona norte, 3 dormitorios"
}{
"succeeded": true,
"message": "Activity created successfully",
"data": {
"id": 40323,
"activityType": "Llamada",
"description": "Llamar al cliente para confirmar interés en departamentos",
"notes": "Cliente mencionó preferencia por zona norte, 3 dormitorios",
"scheduledAt": "2025-10-20T15:00:00",
"status": "Pendiente",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"createdAt": "2025-10-05T22:08:05.69",
"updatedAt": null,
"seller": {
"firstName": "María",
"paternalSurname": "González",
"maternalSurname": "Rojas",
"fullName": "María González Rojas",
"email": "maria.gonzalez@inmobiliaria.com",
"phone": "+51960859432"
}
},
"statusCode": 201
}/external/leads/{leadId}/activities/{activityId} Actualizar Actividad
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead propietario de la actividad |
activityId | integer | ID de la actividad a actualizar |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
levelInterest | integer | Sí | Nivel de interés actualizado (ver endpoint de nivel de interes). Debe ser mayor a 0 |
description | string | Sí | Nueva descripción de la actividad. No puede estar vacía |
scheduledAt | datetime | Sí | Nueva fecha y hora programada. Debe ser una fecha futura |
notes | string | No | Notas actualizadas |
levelInterest, description y scheduledAt por lo que se envíe en el body (no es una actualización parcial); los tres campos son obligatorios en cada solicitud. Solo se pueden actualizar actividades con estado "Pendiente". Las actividades completadas no son modificables.
PUT /external/leads/{leadId}/activities/{activityId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"levelInterest": 3,
"description": "Seguimiento de propuesta enviada",
"scheduledAt": "2025-10-15T16:00:00",
"notes": "Cliente solicitó información adicional sobre financiamiento"
}{
"succeeded": true,
"message": "Activity updated successfully",
"data": {
"id": 40322,
"activityType": "Llamada",
"description": "Seguimiento de propuesta enviada",
"notes": "Cliente solicitó información adicional sobre financiamiento",
"scheduledAt": "2025-10-15T16:00:00",
"status": "Pendiente",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"createdAt": "2025-10-05T19:54:16.4",
"updatedAt": "2025-10-05T22:10:45.827",
"seller": {
"firstName": "Roberto",
"paternalSurname": "Vega",
"maternalSurname": "López",
"fullName": "Roberto Vega López",
"email": "roberto.vega@inmobiliaria.com",
"phone": "+51923634231"
}
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Datos inválidos o falta información requerida |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | Lead o actividad no encontrada |
409 | Conflict | El lead ya tiene una actividad pendiente, el lead está cerrado, o la actividad ya está completada y no admite cambios |
{
"succeeded": false,
"message": "Conflict: Lead has an existing pending activity. Complete it before creating a new one.",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "No action can be taken because the prospectus is closed.",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "Cannot update a completed activity.",
"data": null,
"statusCode": 409
}/external/leads/{leadId}/appointments Consultar Citas de un Lead
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead del cual consultar citas |
Nota: La respuesta incluye información completa del lead y un array de todas sus citas, ordenadas por fecha de creación. Cada cita incluye los detalles del vendedor asignado.
Campos de la Cita
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único de la cita |
medium | string | Medio de la cita (PRESENCIAL, ZOOM, TEAMS, MEET, VIDEO-WHATSAPP) |
description | string | Descripción de la cita (puede contener HTML) |
location | string/null | Ubicación física (null para citas digitales) |
createdAt | datetime | Fecha de creación de la cita |
scheduledAt | datetime | Fecha y hora programada |
status | string | Estado actual: Pendiente, Completada, Vencida |
completedComments | string/null | Comentarios al completar la cita |
completedAt | datetime/null | Fecha de finalización de la cita |
userCompleted | string/null | Nombre del usuario que completó la cita |
seller | object | Información completa del vendedor asignado |
{
"succeeded": true,
"message": "Appointments retrieved successfully",
"data": {
"lead": {
"id": 19985,
"fullName": "Patricia Flores Gutiérrez",
"phone": "+51906250790",
"email": "patricia.flores@example.com"
},
"appointments": [
{
"id": 2601,
"medium": "PRESENCIAL",
"description": "<p>Cliente muy interesado para adquirir su casa</p>",
"location": "Oficina Santa Isabel",
"createdAt": "2025-09-18T19:58:33.14",
"scheduledAt": "2025-09-27T19:57:00",
"status": "Vencida",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"seller": {
"firstName": "Jorge",
"paternalSurname": "Salazar",
"maternalSurname": "Cruz",
"fullName": "Jorge Salazar Cruz",
"email": "jorge.salazar@inmobiliaria.com",
"phone": "+51924124093"
}
},
{
"id": 2599,
"medium": "ZOOM",
"description": "<p>Presentación virtual del proyecto residencial</p>",
"location": null,
"createdAt": "2025-09-15T14:30:00",
"scheduledAt": "2025-09-20T16:00:00",
"status": "Completada",
"completedComments": "Cita exitosa, cliente solicitó cotización",
"completedAt": "2025-09-20T17:15:00",
"userCompleted": "Jorge Salazar Cruz",
"seller": {
"firstName": "Jorge",
"paternalSurname": "Salazar",
"maternalSurname": "Cruz",
"fullName": "Jorge Salazar Cruz",
"email": "jorge.salazar@inmobiliaria.com",
"phone": "+51924124093"
}
}
]
},
"statusCode": 200
}/external/leads/{leadId}/appointments Gestión de Citas
Permite crear y gestionar citas para leads. El sistema automáticamente asigna la cita al vendedor del lead.
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
scheduledAt | datetime | Sí | Fecha y hora programada en hora local (UTC-5, Lima-Perú). |
mediumType | string | Sí | Tipo: "P" (Presencial), "D" (Digital/Virtual) |
medium | string | Sí | Presencial: "PRESENCIAL". Digital: "ZOOM", "TEAMS", "MEET", "VIDEO-WHATSAPP" |
location | string | Condicional | Ubicación física (requerido si mediumType = "P") |
meetingUrl | string | Condicional | URL de reunión (requerido si mediumType = "D") |
notes | string | No | Notas adicionales sobre la cita |
Estados de Cita
- Pendiente: Cita creada, esperando ejecución
- Completada: Cita realizada exitosamente
- Vencida: Cita no gestionada en la fecha programada
Tipos de Medio
| MediumType | Medium | Descripción |
|---|---|---|
P | PRESENCIAL | Cita en persona, requiere location |
D | ZOOM | Reunión por Zoom, requiere meetingUrl |
D | TEAMS | Reunión por Microsoft Teams, requiere meetingUrl |
D | MEET | Reunión por Google Meet, requiere meetingUrl |
D | VIDEO-WHATSAPP | Videollamada por WhatsApp, requiere meetingUrl |
POST /external/leads/{leadId}/appointments
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"scheduledAt": "2025-10-18T17:30:00",
"mediumType": "P",
"medium": "PRESENCIAL",
"location": "Oficina de ventas - Sede Central",
"meetingUrl": null,
"notes": "Cliente interesado en departamentos de 3 dormitorios"
}{
"succeeded": true,
"message": "Appointment created successfully",
"data": {
"id": 2614,
"medium": "PRESENCIAL",
"description": "Cliente interesado en departamentos de 3 dormitorios",
"location": "Oficina de ventas - Sede Central",
"createdAt": "2025-10-05T22:32:24.077",
"scheduledAt": "2025-10-18T17:30:00",
"status": "Pendiente",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"seller": {
"firstName": "Andrea",
"paternalSurname": "Vargas",
"maternalSurname": "Mendoza",
"fullName": "Andrea Vargas Mendoza",
"email": "andrea.vargas@inmobiliaria.com",
"phone": "+51960859432"
}
},
"statusCode": 201
}/external/leads/{leadId}/appointments/{appointmentId} Actualizar Cita
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead propietario de la cita |
appointmentId | integer | ID de la cita a actualizar |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
scheduledAt | datetime | Sí | Nueva fecha y hora programada. Debe ser una fecha futura |
mediumType | string | Sí | Tipo actualizado: "P" (Presencial) o "D" (Digital) |
medium | string | Sí | Medio actualizado: "PRESENCIAL" para "P", o "ZOOM"/"TEAMS"/"MEET"/"VIDEO-WHATSAPP" para "D" |
location | string | Condicional | Nueva ubicación (requerido si mediumType = "P") |
meetingUrl | string | Condicional | Nueva URL de reunión (requerido si mediumType = "D") |
notes | string | No | Notas actualizadas |
scheduledAt, mediumType y medium por lo que se envíe en el body (no es una actualización parcial); los tres campos son obligatorios en cada solicitud. Solo se pueden actualizar citas con estado "Pendiente". Las citas completadas no son modificables.
PUT /external/leads/{leadId}/appointments/{appointmentId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"scheduledAt": "2025-10-20T15:00:00",
"mediumType": "D",
"medium": "MEET",
"location": null,
"meetingUrl": "https://meet.google.com/xyz-abcd-efg",
"notes": "Cambio a modalidad virtual por solicitud del cliente"
}{
"succeeded": true,
"message": "Appointment updated successfully",
"data": {
"id": 2613,
"medium": "MEET",
"description": "Cambio a modalidad virtual por solicitud del cliente",
"location": null,
"createdAt": "2025-10-05T21:39:22.367",
"scheduledAt": "2025-10-20T15:00:00",
"status": "Pendiente",
"completedComments": null,
"completedAt": null,
"userCompleted": null,
"seller": {
"firstName": "Luis",
"paternalSurname": "Morales",
"maternalSurname": "Ríos",
"fullName": "Luis Morales Ríos",
"email": "luis.morales@inmobiliaria.com",
"phone": "+51960859432"
}
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Datos inválidos o errores de validación |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | Lead o cita no encontrada |
409 | Conflict | El lead ya tiene una cita pendiente, el lead no está en un estado válido, o la cita ya está completada/finalizada y no admite cambios |
Ejemplos de Errores de Validación
- Medio inválido para citas digitales: "For digital appointments, the medium must be one of: ZOOM, TEAMS, MEET, VIDEO-WHATSAPP"
- Falta ubicación para presencial: "Location is required for in-person appointments"
- Falta URL para digital: "The meeting URL is required for digital appointments"
- Fecha inválida: "The appointment date must be in the future"
{
"succeeded": false,
"message": "The lead already has a pending appointment scheduled for 18/10/2025 17:30. You must complete the existing appointment (Successful/Failed) before creating a new one.",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "The lead is not in a valid status to update appointments",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "Cannot update a completed or finalized appointment",
"data": null,
"statusCode": 409
}/external/leads/{leadId}/notes Consultar Notas de un Lead
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead del cual consultar notas |
Nota: La respuesta incluye información completa del lead y todas sus notas ordenadas por fecha de creación descendente (más recientes primero). El campo updatedAt es null si la nota nunca ha sido modificada.
Campos de la Nota
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único de la nota |
title | string | Título de la nota |
content | string | Contenido detallado de la nota |
createdAt | datetime | Fecha y hora de creación (mantiene fecha original) |
updatedAt | datetime/null | Fecha y hora de última modificación |
createdBy | string | Nombre completo del usuario que creó la nota |
{
"succeeded": true,
"message": "Notes retrieved successfully",
"data": {
"lead": {
"id": 8869,
"fullName": "Carmen Rodríguez Vargas",
"phone": "+51924156656",
"email": "carmen.rodriguez@example.com"
},
"notes": [
{
"id": 38,
"title": "Actualización de preferencias",
"content": "Cliente confirma interés en zona norte, prefiere lotes con vista al parque",
"createdAt": "2025-10-05T21:58:03.7",
"updatedAt": "2025-10-05T21:58:49.54",
"createdBy": "Roberto Vega López"
},
{
"id": 37,
"title": "Primera llamada",
"content": "Contacto inicial exitoso, cliente solicita información sobre proyecto residencial",
"createdAt": "2025-10-04T15:30:22.45",
"updatedAt": null,
"createdBy": "Ana Martínez Silva"
}
]
},
"statusCode": 200
}/external/leads/{leadId}/notes Gestión de Notas
Permite agregar, actualizar y consultar notas asociadas a un lead para registro de interacciones y seguimiento.
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
Title | string | Sí | Título descriptivo de la nota |
Content | string | Sí | Contenido detallado de la nota |
POST /external/leads/{leadId}/notes
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"Title": "Seguimiento importante",
"Content": "Cliente interesado en lotes de 150m², consultar disponibilidad en Manzana C. Solicita información sobre financiamiento directo."
}{
"succeeded": true,
"message": "Note created successfully",
"data": {
"id": 39,
"title": "Seguimiento importante",
"content": "Cliente interesado en lotes de 150m², consultar disponibilidad en Manzana C. Solicita información sobre financiamiento directo.",
"createdAt": "2025-10-05T22:41:25.9579501-05:00",
"updatedAt": null,
"createdBy": "Ana Martínez Silva"
},
"statusCode": 201
}/external/leads/{leadId}/notes/{noteId} Actualizar Nota
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
leadId | integer | ID del lead en la ruta. No se usa para validar la propiedad de la nota; la actualización se realiza únicamente por noteId |
noteId | integer | ID de la nota a actualizar |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
Title | string | Sí | Nuevo título de la nota |
Content | string | Sí | Nuevo contenido de la nota |
Importante: El campo createdAt mantiene la fecha original de creación, mientras que updatedAt se actualiza con la fecha y hora de la modificación.
PUT /external/leads/{leadId}/notes/{noteId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"Title": "Seguimiento actualizado",
"Content": "Cliente decidió por lote esquinero en Manzana D, coordinar visita para esta semana"
}{
"succeeded": true,
"message": "Notes updated successfully",
"data": {
"id": 34,
"title": "Seguimiento actualizado",
"content": "Cliente decidió por lote esquinero en Manzana D, coordinar visita para esta semana",
"createdAt": "2025-10-05T11:26:08.197",
"updatedAt": "2025-10-05T22:42:47.3860893-05:00",
"createdBy": "Roberto Vega López"
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Errores de validación en los datos enviados |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | Lead o nota no encontrada |
Nota: Cuando un lead no tiene notas, la respuesta es exitosa (200) pero el array notes está vacío.
{
"succeeded": false,
"message": "Validation failed",
"errors": [
"The Title field is required.",
"The Content field is required."
],
"data": null,
"statusCode": 400
}{
"succeeded": false,
"message": "Lead not found",
"data": null,
"statusCode": 404
}{
"succeeded": true,
"message": "No notes found for the specified lead",
"data": {
"lead": {
"id": 8869,
"fullName": "Carmen Rodríguez Vargas",
"phone": "+51924156656",
"email": "carmen.rodriguez@example.com"
},
"notes": []
},
"statusCode": 200
}/external/clients/sales?startDate={startDate}&endDate={endDate} Ventas de Clientes
Obtiene datos de ventas de todos los clientes con filtros de fecha, útil para consultar documentos separación y venta en un rango temporal específico.
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
startDate | datetime | No | Fecha inicial del rango (por defecto: primer día del mes actual) |
endDate | datetime | No | Fecha final del rango (por defecto: último día del mes actual) |
pageNumber | integer | No | Número de página a consultar, base 1 (por defecto: 1) |
pageSize | integer | No | Registros por página (por defecto y máximo: 500) |
startDate y endDate es de 1 año (365 días). Rangos mayores retornarán un error 400.
data), con un máximo de 500 registros por página. Si pageSize excede 500, se limita automáticamente a 500. Los metadatos de paginación se devuelven tanto en el cuerpo de la respuesta como en los headers X-Total-Count, X-Page-Number, X-Page-Size y X-Max-Page-Size.
Estructura de Datos del Cliente
| Campo | Tipo | Descripción |
|---|---|---|
documentNumber | string | Número de documento del cliente (DNI, RUC, etc.) |
fullName | string | Nombre completo del cliente |
firstName | string | Nombres del cliente |
paternalSurname | string | Apellido paterno |
maternalSurname | string | Apellido materno |
phone | string | Número de teléfono con código de país |
email | string | Correo electrónico del cliente |
gender | string | Género (Masculino/Femenino) |
birthDate | datetime | Fecha de nacimiento |
address | string | Dirección completa |
department | string | Departamento de residencia |
province | string | Provincia de residencia |
district | string | Distrito de residencia |
documents | array | Lista de documentos de venta asociados al cliente |
Estructura del Documento de Venta
| Campo | Tipo | Descripción |
|---|---|---|
proformaId | integer | ID único deL documento |
correlative | string | Número correlativo del documento (YYYYMM-XXXXXXXXX) |
status | string | Estado del documento (En Proceso Separación/Separación/Venta) |
startDate | datetime | Fecha de inicio del proceso actual |
endDate | datetime | Fecha de finalización del proceso actual (null si está activo) |
proformaStartDate | datetime | null | Fecha de creación de la proforma |
separationStartDate | datetime | null | Fecha de entrada al estado Separación |
separationEndDate | datetime | null | Fecha de salida del estado Separación (null si sigue vigente) |
saleStartDate | datetime | null | Fecha de entrada al estado Venta |
saleEndDate | datetime | null | Fecha de salida del estado Venta (null si sigue vigente) |
seller | string | Nombre del vendedor asignado |
project | string | Nombre del proyecto inmobiliario |
stage | string | Etapa del proyecto |
units | array | Unidades incluidas en el documento |
financing | object | Detalles del financiamiento |
Estructura de Unidad
| Campo | Tipo | Descripción |
|---|---|---|
unitNumber | string | Número identificador de la unidad |
unitArea | decimal | Área de la unidad en m² |
basePrice | decimal | Precio base de lista |
unitPrice | decimal | Precio final con descuentos aplicados |
discountPercentage | decimal | Porcentaje de descuento aplicado |
discount | decimal | Monto total del descuento |
total | decimal | Precio total de la unidad |
Estructura de Financiamiento
| Campo | Tipo | Descripción |
|---|---|---|
financingType | string | Tipo de financiamiento (Contado/Financiado) |
currency | string | Moneda (PEN/USD) |
initialInstallments | integer | Número de cuotas iniciales |
financingInstallments | integer | Número de cuotas de financiamiento |
totalInstallments | integer | Total de cuotas |
reservationAmount | decimal | Monto de reserva |
downPayment | decimal | Monto de cuota inicial/enganche |
amountToFinance | decimal | Monto a financiar |
totalPaid | decimal | Total pagado hasta el momento |
totalPending | decimal | Total pendiente de pago |
Flujo de estados:
Principal: Proforma → En Proceso Separación → Separación → En Proceso Venta → Venta
Devolución (opcional): Venta → En Proceso Devolución → Devolución
GET /external/clients/sales?startDate={startDate}&endDate={endDate}&pageNumber={pageNumber}&pageSize={pageSize}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}X-Total-Count: 8
X-Page-Number: 1
X-Page-Size: 500
X-Max-Page-Size: 500{
"succeeded": true,
"message": "Client sales data retrieved successfully",
"pageNumber": 1,
"pageSize": 500,
"totalRecords": 8,
"totalPages": 1,
"data": [
{
"documentNumber": "12345678",
"fullName": "JUAN CARLOS GARCIA LOPEZ",
"firstName": "JUAN CARLOS",
"paternalSurname": "GARCIA",
"maternalSurname": "LOPEZ",
"phone": "+51987654321",
"email": "jgarcia@email.com",
"gender": "Masculino",
"birthDate": "1985-05-15T00:00:00",
"address": "AV. PRINCIPAL 123",
"department": "LIMA",
"province": "LIMA",
"district": "MIRAFLORES",
"documents": [
{
"proformaId": 1001,
"correlative": "202510-000000123",
"status": "En Proceso Separación",
"startDate": "2025-10-15T10:30:00",
"endDate": null,
"proformaStartDate": "2025-10-15T10:20:00",
"separationStartDate": "2025-10-15T10:30:00",
"separationEndDate": null,
"saleStartDate": null,
"saleEndDate": null,
"seller": "MARIA FERNANDEZ TORRES",
"project": "RESIDENCIAL VERDE",
"stage": "ETAPA 1",
"units": [
{
"unitNumber": "B-15",
"unitArea": 85.50,
"basePrice": 45000.00,
"unitPrice": 42000.00,
"discountPercentage": 6.67,
"discount": 3000.00,
"total": 42000.00
}
],
"financing": {
"financingType": "Financiado",
"currency": "PEN",
"initialInstallments": 2,
"financingInstallments": 24,
"totalInstallments": 26,
"reservationAmount": 500.00,
"downPayment": 4200.00,
"amountToFinance": 37300.00,
"totalPaid": 500.00,
"totalPending": 41500.00
}
}
]
},
{
"documentNumber": "87654321",
"fullName": "ANA LUCIA MARTINEZ SILVA",
"firstName": "ANA LUCIA",
"paternalSurname": "MARTINEZ",
"maternalSurname": "SILVA",
"phone": "+51912345678",
"email": "amartinez@email.com",
"gender": "Femenino",
"birthDate": "1990-08-22T00:00:00",
"address": "CALLE LAS FLORES 456",
"department": "AREQUIPA",
"province": "AREQUIPA",
"district": "CAYMA",
"documents": [
{
"proformaId": 1002,
"correlative": "202510-000000124",
"status": "Venta",
"startDate": "2025-10-10T14:15:00",
"endDate": "2025-10-18T16:00:00",
"proformaStartDate": "2025-10-10T14:00:00",
"separationStartDate": "2025-10-10T14:15:00",
"separationEndDate": "2025-10-18T15:45:00",
"saleStartDate": "2025-10-18T15:45:00",
"saleEndDate": "2025-10-18T16:00:00",
"seller": "CARLOS MENDOZA RUIZ",
"project": "MIRADOR DEL SUR",
"stage": "ETAPA 2",
"units": [
{
"unitNumber": "C-08",
"unitArea": 72.00,
"basePrice": 38500.00,
"unitPrice": 35000.00,
"discountPercentage": 9.09,
"discount": 3500.00,
"total": 35000.00
}
],
"financing": {
"financingType": "Contado",
"currency": "USD",
"initialInstallments": 1,
"financingInstallments": 0,
"totalInstallments": 1,
"reservationAmount": 0.00,
"downPayment": 35000.00,
"amountToFinance": 0.00,
"totalPaid": 35000.00,
"totalPending": 0.00
}
}
]
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Rango de fechas inválido o excede 1 año |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Cuando no hay ventas en el período especificado, la respuesta es exitosa (200) pero el array data está vacío.
{
"succeeded": false,
"message": "Date range cannot exceed 1 year (365 days)",
"data": null,
"statusCode": 400
}{
"succeeded": true,
"message": "No client sales found for the specified date range",
"pageNumber": 1,
"pageSize": 500,
"totalRecords": 0,
"totalPages": 0,
"data": [],
"statusCode": 200
}/external/clients/{documentNumber}/sales Ventas de Cliente por Documento
Obtiene todos los datos de ventas asociados a un cliente específico utilizando su número de documento, incluyendo todas sus proformas, separaciones y ventas históricas.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
documentNumber | string | Sí | Número de documento del cliente (DNI, RUC, Carnet de Extranjería, etc.) |
Nota: La respuesta incluye todos los documentos históricos del cliente, ordenados cronológicamente. Un cliente puede tener múltiples documentos en diferentes estados.
Estructura de la Respuesta
La estructura de datos es idéntica al endpoint de ventas generales, pero retorna la información de un único cliente en lugar de un array.
| Campo Raíz | Tipo | Descripción |
|---|---|---|
succeeded | boolean | Indica si la operación fue exitosa |
message | string | Mensaje descriptivo del resultado |
data | object | null | Objeto con información del cliente y sus documentos, o null si no se encontraron ventas para el documento indicado |
statusCode | integer | Código de estado HTTP |
Información del Cliente
El objeto data contiene los siguientes campos del cliente:
documentNumber: Número de documentofullName: Nombre completofirstName: NombrespaternalSurname: Apellido paternomaternalSurname: Apellido maternophone: Teléfono con código de paísemail: Correo electrónicogender: Género (Masculino/Femenino)birthDate: Fecha de nacimientoaddress: Dirección completadepartment: Departamentoprovince: Provinciadistrict: Distritodocuments: Array de documentos de venta
GET /external/clients/{documentNumber}/sales
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}GET /external/clients/12345678/sales
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Subdomain: miempresa{
"succeeded": true,
"message": "Client sales data retrieved successfully",
"data": {
"documentNumber": "12345678",
"fullName": "ROBERTO CARLOS MENDEZ VARGAS",
"firstName": "ROBERTO CARLOS",
"paternalSurname": "MENDEZ",
"maternalSurname": "VARGAS",
"phone": "+51998765432",
"email": "rmendez@email.com",
"gender": "Masculino",
"birthDate": "1982-11-08T00:00:00",
"address": "JR. LOS PINOS 789",
"department": "CUSCO",
"province": "CUSCO",
"district": "WANCHAQ",
"documents": [
{
"proformaId": 2001,
"correlative": "202509-000000567",
"status": "Separación",
"startDate": "2025-09-20T11:30:00",
"endDate": "2025-09-25T14:20:00",
"proformaStartDate": "2025-09-20T11:15:00",
"separationStartDate": "2025-09-20T11:30:00",
"separationEndDate": "2025-09-25T14:20:00",
"saleStartDate": null,
"saleEndDate": null,
"seller": "PATRICIA GOMEZ RIOS",
"project": "CIUDAD JARDÍN",
"stage": "ETAPA 3",
"units": [
{
"unitNumber": "E-22",
"unitArea": 68.00,
"basePrice": 36500.00,
"unitPrice": 33850.00,
"discountPercentage": 7.26,
"discount": 2650.00,
"total": 33850.00
}
],
"financing": {
"financingType": "Financiado",
"currency": "PEN",
"initialInstallments": 1,
"financingInstallments": 48,
"totalInstallments": 49,
"reservationAmount": 200.00,
"downPayment": 3385.00,
"amountToFinance": 30265.00,
"totalPaid": 3585.00,
"totalPending": 30265.00
}
}
]
},
"statusCode": 200
}Estados de Documento
Los documentos pueden tener los siguientes estados en su ciclo de vida:
Flujo Principal
| Estado | Descripción | Siguiente Estado |
|---|---|---|
Proforma | Documento inicial de cotización sin compromiso | En Proceso Separación |
En Proceso Separación | Se está gestionando la separación de la unidad | Separación |
Separación | Unidad separada con compromiso de compra | En Proceso Venta |
En Proceso Venta | Se está tramitando la formalización de la venta | Venta |
Venta | Transacción completada y formalizada | En Proceso Devolución (opcional) |
Flujo de Devolución
| Estado | Descripción | Siguiente Estado |
|---|---|---|
En Proceso Devolución | Se está gestionando la devolución de la unidad vendida | Devolución |
Devolución | Devolución completada, unidad liberada | - |
Flujo completo:
Principal: Proforma → En Proceso Separación → Separación → En Proceso Venta → Venta
Devolución (opcional): Venta → En Proceso Devolución → Devolución
Nota: Las fechas proformaStartDate, separationStartDate, saleStartDate y returnStartDate registran cuándo el documento entró en cada estado. Las fechas endDate correspondientes indican cuándo finalizó ese estado (o son null si es el estado actual).
Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Este endpoint acepta cualquier valor de documentNumber sin validación previa. Si el cliente no existe o no tiene ventas registradas, la respuesta es igualmente exitosa (200) con data en null.
{
"succeeded": true,
"message": "No sales data found for client with document number: 12345678",
"data": null,
"statusCode": 200
}/external/clients/{documentNumber}/sales/{saleId} Detalle de Venta por ID
Obtiene el detalle completo de un documento de venta específico a partir del número de documento del cliente y el ID de la proforma.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
documentNumber | string | Sí | Número de documento del cliente (DNI, RUC, Carnet de Extranjería, etc.) |
saleId | integer | Sí | Identificador único de la proforma / venta |
Estructura de la Respuesta
| Campo Raíz | Tipo | Descripción |
|---|---|---|
succeeded | boolean | Indica si la operación fue exitosa |
message | string | Mensaje descriptivo del resultado |
data | object | null | Objeto con el detalle de la venta, o null si no se encontró |
statusCode | integer | Código de estado HTTP |
Objeto data
proformaId: ID único del documentocorrelative: Correlativo del documento (formatoYYYYMM-NNNNNNNNN)status: Estado actual del documento (ver tabla de estados)startDate/endDate: Fecha de inicio y cierre del documentoproformaStartDate: Fecha de creación de la proformaseparationStartDate/separationEndDate: Fechas de entrada y salida del estado SeparaciónsaleStartDate/saleEndDate: Fechas de entrada y salida del estado Ventaseller: Nombre completo del vendedor asignadoproject: Nombre del proyecto inmobiliariostage: Etapa del proyectoclient: Información resumida del cliente (ver objetoclient)units: Array de unidades incluidas en la venta
Objeto client
documentNumber: Número de documentofullName: Nombre completofirstName: NombrespaternalSurname: Apellido paternomaternalSurname: Apellido maternophone: Teléfono con código de paísemail: Correo electrónico
Objeto units[]
| Campo | Tipo | Descripción |
|---|---|---|
unitNumber | string | Identificador de la unidad (lote, departamento, etc.) |
unitArea | decimal | Área en m² |
basePrice | decimal | Precio base sin descuentos |
unitPrice | decimal | Precio de venta final |
discountPercentage | decimal | Porcentaje de descuento aplicado |
discount | decimal | Monto del descuento |
total | decimal | Monto total a pagar |
Nota: A diferencia del endpoint de ventas por cliente, este endpoint no incluye el objeto financing. Solo retorna los datos del documento, el cliente resumido y las unidades asociadas.
GET /external/clients/{documentNumber}/sales/{saleId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}GET /external/clients/02865709/sales/259
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Subdomain: miempresa{
"succeeded": true,
"message": "Sale detail retrieved successfully",
"data": {
"proformaId": 259,
"correlative": "202603-000000111",
"status": "Ventas",
"startDate": "2022-09-22T17:12:46",
"endDate": "2022-09-22T17:12:46",
"proformaStartDate": "2022-09-22T15:58:11",
"separationStartDate": "2022-09-22T17:12:37",
"separationEndDate": "2022-09-22T17:12:37",
"saleStartDate": "2022-09-22T17:12:46",
"saleEndDate": "2022-09-22T17:12:46",
"seller": "ANDRES GARCIA JUAREZ",
"project": "LIMA VERDE",
"stage": "ETAPA 1",
"client": {
"documentNumber": "00000002",
"fullName": "ROBERTO QUEVEDO JUAREZ",
"firstName": "ROBERTO",
"paternalSurname": "QUEVEDO",
"maternalSurname": "JUAREZ",
"phone": "+51987654321",
"email": "correo@hotmail.com"
},
"units": [
{
"unitNumber": "L-01",
"unitArea": 189.86,
"basePrice": 140686.26,
"unitPrice": 140686.26,
"discountPercentage": 0.0,
"discount": 0.0,
"total": 140686.26
}
]
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No existe una venta con ese ID asociada al documento indicado |
Nota: Este endpoint no valida el formato de documentNumber. El error 404 se devuelve tanto cuando el documentNumber o el saleId no existen en el sistema como cuando existen pero no están relacionados entre sí (la venta no pertenece a ese cliente). Esto evita exponer información de otros clientes.
{
"succeeded": false,
"message": "No sale found for document 02865709 with id 25922",
"data": null,
"statusCode": 404
}/external/payment-schedules/{correlative} Cronograma de Pagos
Obtiene el cronograma completo de pagos mediante el correlativo de proforma, incluyendo el encabezado del cronograma, todas las cuotas y la próxima cuota por vencer.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
correlative | string | Sí | Número correlativo del documento (ej: 202510-000000123) |
Nota: El cronograma incluye cuotas iniciales (sin interés) y cuotas financiadas (con interés calculado). La respuesta muestra el detalle completo de cada cuota, incluyendo penalidades si aplican.
GET /external/payment-schedules/{correlative}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}GET /external/payment-schedules/202510-000000123
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Subdomain: miempresa{
"succeeded": true,
"message": "Payment schedule retrieved successfully",
"data": {
"scheduleId": 501,
"proformaId": 1001,
"correlative": "202510-000000123",
"productPrice": 42000.00,
"initialFeePercentage": 10.00,
"initialGross": 4200.00,
"reservationAmount": 500.00,
"netInitial": 3700.00,
"numberInitialInstallments": 2,
"downPaymentInitial": 1850.00,
"dateBeginInitial": "2025-11-01T00:00:00",
"tea": 12.50,
"tem": 0.99,
"numberFinancingInstallments": 24,
"amountToFinance": 37300.00,
"downPaymentFinancing": 1621.83,
"dateBeginFinancing": "2026-01-01T00:00:00",
"totalPrincipal": 37300.00,
"totalInterest": 5412.00,
"totalPayment": 42712.00,
"totalPaid": 3850.00,
"totalPending": 38862.00,
"overdueCuotas": 1,
"installmentCount": 26,
"paymentFrequency": "MONTHLY",
"firstPaymentDate": "2025-11-01T00:00:00",
"currency": "PEN",
"createdBy": "MARIA FERNANDEZ TORRES",
"createdAt": "2025-10-15T10:30:00",
"installments": [
{
"scheduleDetId": 5001,
"scheduleId": 501,
"label": "Cuota Inicial",
"installmentNumber": 1,
"dueDate": "2025-11-01T00:00:00",
"balance": 42000.00,
"principal": 1850.00,
"interest": 0.00,
"payment": 1850.00,
"paidPrincipal": 1850.00,
"paidInterest": 0.00,
"paidPenalty": 0.00,
"totalPaidAmount": 1850.00,
"remainingBalance": 40150.00,
"status": "PAID",
"paymentDate": "2025-10-28T14:30:00"
},
{
"scheduleDetId": 5002,
"scheduleId": 501,
"label": "Cuota Inicial",
"installmentNumber": 2,
"dueDate": "2025-12-01T00:00:00",
"balance": 40150.00,
"principal": 1850.00,
"interest": 0.00,
"payment": 1850.00,
"paidPrincipal": 1850.00,
"paidInterest": 0.00,
"paidPenalty": 0.00,
"totalPaidAmount": 1850.00,
"remainingBalance": 38300.00,
"status": "PAID",
"paymentDate": "2025-11-29T16:45:00"
},
{
"scheduleDetId": 5003,
"scheduleId": 501,
"label": "Saldo a Financiar",
"installmentNumber": 3,
"dueDate": "2026-01-01T00:00:00",
"balance": 38300.00,
"principal": 1552.17,
"interest": 69.66,
"payment": 1621.83,
"paidPrincipal": 150.00,
"paidInterest": 0.00,
"paidPenalty": 25.00,
"totalPaidAmount": 175.00,
"remainingBalance": 36747.83,
"status": "PARTIAL",
"paymentDate": "2026-01-15T10:20:00"
},
{
"scheduleDetId": 5004,
"scheduleId": 501,
"label": "Saldo a Financiar",
"installmentNumber": 4,
"dueDate": "2026-02-01T00:00:00",
"balance": 36747.83,
"principal": 1567.52,
"interest": 54.31,
"payment": 1621.83,
"paidPrincipal": 0.00,
"paidInterest": 0.00,
"paidPenalty": 0.00,
"totalPaidAmount": 0.00,
"remainingBalance": 36747.83,
"status": "OVERDUE",
"paymentDate": null
},
{
"scheduleDetId": 5005,
"scheduleId": 501,
"label": "Saldo a Financiar",
"installmentNumber": 5,
"dueDate": "2026-03-01T00:00:00",
"balance": 35180.31,
"principal": 1582.98,
"interest": 38.85,
"payment": 1621.83,
"paidPrincipal": 0.00,
"paidInterest": 0.00,
"paidPenalty": 0.00,
"totalPaidAmount": 0.00,
"remainingBalance": 35180.31,
"status": "PENDING",
"paymentDate": null
}
],
"nextInstallmentDue": {
"scheduleDetId": 5004,
"scheduleId": 501,
"label": "Saldo a Financiar",
"installmentNumber": 4,
"dueDate": "2026-02-01T00:00:00",
"balance": 36747.83,
"principal": 1567.52,
"interest": 54.31,
"payment": 1621.83,
"status": "OVERDUE"
}
},
"statusCode": 200
}Estructura del Cronograma de Pagos
Encabezado del Cronograma
| Campo | Tipo | Descripción |
|---|---|---|
scheduleId | integer | ID único del cronograma de pagos |
proformaId | integer | ID del documento asociado |
correlative | string | Número correlativo del documento |
productPrice | decimal | Precio total del producto/unidad |
initialFeePercentage | decimal | Porcentaje de cuota inicial |
initialGross | decimal | Monto bruto de cuota inicial |
reservationAmount | decimal | Monto de reserva |
netInitial | decimal | Cuota inicial neta (bruto - reserva) |
numberInitialInstallments | integer | Número de cuotas iniciales |
downPaymentInitial | decimal | Monto de cada cuota inicial |
dateBeginInitial | datetime | Fecha de inicio de cuotas iniciales |
tea | decimal | Tasa Efectiva Anual (%) |
tem | decimal | Tasa Efectiva Mensual (%) |
numberFinancingInstallments | integer | Número de cuotas financiadas |
amountToFinance | decimal | Monto total a financiar |
downPaymentFinancing | decimal | Monto de cada cuota financiada |
dateBeginFinancing | datetime | Fecha de inicio de cuotas financiadas |
totalPrincipal | decimal | Total del capital/principal |
totalInterest | decimal | Total de intereses |
totalPayment | decimal | Pago total (principal + intereses) |
totalPaid | decimal | Total pagado hasta el momento |
totalPending | decimal | Total pendiente de pago |
overdueCuotas | integer | Número de cuotas vencidas |
installmentCount | integer | Total de cuotas en el cronograma |
paymentFrequency | string | Frecuencia de pago (Mensual) |
firstPaymentDate | datetime | Fecha del primer pago |
currency | string | Moneda (PEN/USD) |
createdBy | string | Usuario que creó el cronograma |
createdAt | datetime | Fecha de creación del cronograma |
installments | array | Lista de todas las cuotas del cronograma |
nextInstallmentDue | object | Próxima cuota por vencer o vencida (puede ser null) |
Estructura de Cuota (Installment)
| Campo | Tipo | Descripción |
|---|---|---|
scheduleDetId | integer | ID único del detalle de cuota |
scheduleId | integer | ID del cronograma padre |
label | string | Etiqueta descriptiva de la cuota |
installmentNumber | integer | Número de cuota secuencial |
dueDate | datetime | Fecha de vencimiento de la cuota |
balance | decimal | Saldo antes de esta cuota |
principal | decimal | Monto del capital en esta cuota |
interest | decimal | Monto de interés en esta cuota |
payment | decimal | Pago total de la cuota (principal + interés) |
paidPrincipal | decimal | Capital pagado de esta cuota |
paidInterest | decimal | Interés pagado de esta cuota |
paidPenalty | decimal | Penalidad pagada (por mora) |
totalPaidAmount | decimal | Total pagado de esta cuota (no incluye penalidades) |
remainingBalance | decimal | Saldo restante después de esta cuota |
status | string | Estado de la cuota (PAID/PARTIAL/OVERDUE/PENDING) |
paymentDate | datetime | Fecha de pago real (null si no ha sido pagada) |
Próxima Cuota por Vencer (NextInstallmentDue)
| Campo | Tipo | Descripción |
|---|---|---|
scheduleDetId | integer | ID único del detalle de cuota |
scheduleId | integer | ID del cronograma padre |
label | string | Etiqueta descriptiva de la cuota |
installmentNumber | integer | Número de cuota secuencial |
dueDate | datetime | Fecha de vencimiento |
balance | decimal | Saldo antes de esta cuota |
principal | decimal | Monto del capital |
interest | decimal | Monto de interés |
payment | decimal | Pago total requerido |
status | string | Estado de la cuota (OVERDUE/PENDING) |
Estados de Cuota
Las cuotas pueden tener los siguientes estados:
| Estado | Descripción | Condición |
|---|---|---|
PAID | Cuota pagada completamente | totalPaidAmount = payment |
PARTIAL | Cuota pagada parcialmente | 0 < totalPaidAmount < payment |
OVERDUE | Cuota no pagada y fecha vencida | dueDate < hoy AND totalPaidAmount = 0 |
PENDING | Cuota por vencer, aún no pagada | dueDate >= hoy AND totalPaidAmount = 0 |
Importante: Las cuotas con estado "OVERDUE" o "PARTIAL" pueden generar penalidades por mora. El campo paidPenalty muestra el monto de penalidad pagado si aplica.
Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | Cronograma de pagos no encontrado para el correlativo indicado |
{
"succeeded": false,
"message": "Payment schedule not found for correlative: 202510-000000123",
"data": null,
"statusCode": 404
}/external/payment-schedules/{paymentScheduleId}/installments/{installmentId}/payments Registrar Pago de Cuota
Permite registrar un pago sobre una cuota específica de un cronograma de pagos. El sistema valida que la cuota no esté completamente pagada y que el monto sea válido.
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
paymentScheduleId | integer | ID del cronograma de pagos |
installmentId | integer | ID de la cuota a la que se registra el pago |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
ProformaId | integer | Sí | ID de la proforma/Venta asociada al pago |
PaymentMethod | string | Sí | Método de pago utilizado (ver tabla de métodos permitidos) |
PaymentDate | date | Sí | Fecha en que se realizó el pago en formato YYYY-MM-DD. No puede ser una fecha futura. |
ReferenceNumber | string | Condicional | Número de operación o referencia del comprobante de pago |
Notes | string | No | Observaciones adicionales sobre el pago |
PaymentMethod es TRANSFER, CARD o DEPOSIT, y también cuando es CHECK. Es opcional cuando PaymentMethod es CASH.
Métodos de Pago Permitidos
| Valor | Descripción |
|---|---|
TRANSFER | Transferencia bancaria |
CASH | Efectivo |
CHECK | Cheque |
DEPOSIT | Depósito bancario |
CARD | Tarjeta Credito/Debito |
Campos de la Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
paymentId | integer | ID único del pago registrado |
paymentNumber | string | Número de pago generado automáticamente |
message | string | Mensaje descriptivo del resultado, indica si se aplicó mora o no |
POST /external/payment-schedules/{paymentScheduleId}/installments/{installmentId}/payments
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"ProformaId": 271,
"PaymentMethod": "TRANSFER",
"PaymentDate": "2026-04-02",
"ReferenceNumber": "000000245",
"Notes": "Pago realizado vía transferencia bancaria BCP"
}{
"succeeded": true,
"message": "Payment created successfully",
"data": {
"paymentId": 107,
"paymentNumber": "PAY-271-20260402-00107",
"message": "Installment payment registered successfully (no penalty)"
},
"statusCode": 201
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Datos inválidos, método de pago inválido, monto excede el saldo pendiente o fecha futura |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | La cuota especificada no existe o está inactiva |
409 | Conflict | Cronograma inactivo, refinanciamiento pendiente de aprobación, cuota ya pagada completamente, o cuotas anteriores pendientes de pago |
{
"succeeded": false,
"message": "The specified installment does not exist or is inactive",
"data": null,
"statusCode": 404
}{
"succeeded": false,
"message": "Cannot register payments while a refinancing request is pending approval. Approve or reject it first.",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "The installment is already fully paid",
"data": null,
"statusCode": 409
}/external/payment-schedules/{paymentScheduleId}/installments/{installmentId}/payments/{paymentId}/reversals Extornar Pago
Permite revertir un pago previamente registrado sobre una cuota. El extorno anula el efecto del pago en el cronograma, dejando la cuota en su estado anterior.
Parámetros de Ruta
| Parámetro | Tipo | Descripción |
|---|---|---|
paymentScheduleId | integer | ID del cronograma de pagos |
installmentId | integer | ID de la cuota a la que pertenece el pago |
paymentId | integer | ID del pago a extornar |
Parámetros del Body
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
ReasonCode | string | Sí | Código del motivo del extorno (ver tabla de códigos permitidos) |
Notes | string | Sí | Observaciones del extorno. Mínimo 10 caracteres. |
Códigos de Motivo Permitidos
GET /external/catalogs/reversal-reason-codes.
| Valor | Descripción |
|---|---|
ADMIN_ERROR | Error administrativo |
AMOUNT_ERROR | Error en el monto registrado |
CLIENT_REQUEST | Solicitud del cliente |
DUPLICATE_PAYMENT | Pago duplicado |
FRAUD_DETECTED | Fraude detectado |
INSUFFICIENT_FUNDS | Fondos insuficientes |
METHOD_ERROR | Error en el método de pago |
OPERATION_CANCELLED | Cancelación de operación |
OTHER | Otros motivos (detallar en Notes) |
SYSTEM_ERROR | Error del sistema |
Campos de la Respuesta
| Campo | Tipo | Descripción |
|---|---|---|
reversalId | integer | ID único del extorno registrado |
paymentId | integer | ID del pago que fue extornado |
reversalNumber | string | Número de extorno generado automáticamente con formato REV-{paymentNumber}-{fecha}-{secuencial} |
message | string | Mensaje confirmando el resultado del extorno |
POST /external/payment-schedules/{paymentScheduleId}/installments/{installmentId}/payments/{paymentId}/reversals
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}
Content-Type: application/json
Body:
{
"ReasonCode": "DUPLICATE_PAYMENT",
"Notes": "Por motivos de pago duplicado registrado el 02/04/2026"
}{
"succeeded": true,
"message": "Payment reversal processed successfully",
"data": {
"reversalId": 2,
"paymentId": 107,
"reversalNumber": "REV-PAY-271-20260402-00107-20260402-003",
"message": "Payment reversed successfully"
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | Datos de entrada inválidos (tipo de extorno, motivo, notas o monto de extorno parcial) |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | El pago especificado no existe o está inactivo, o no se encontró el detalle del pago a extornar |
409 | Conflict | Pago ya extornado anteriormente, pago no está en estado ACTIVE, comprobante asociado, o el extorno generaría una inconsistencia con cuotas posteriores ya pagadas |
{
"succeeded": false,
"message": "The specified payment does not exist or is inactive",
"data": null,
"statusCode": 404
}{
"succeeded": false,
"message": "The payment has already been reversed",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "Cannot reverse a payment that already has an associated receipt or invoice",
"data": null,
"statusCode": 409
}{
"succeeded": false,
"message": "The reversal would create an inconsistency: later installments have already been paid",
"data": null,
"statusCode": 409
}/external/catalogs/activity-types Tipos de Actividad
Obtiene el catálogo de tipos de actividad disponibles para clasificar las interacciones y gestiones con clientes potenciales.
Nota: La respuesta incluye todos los tipos de actividad configurados. Se recomienda filtrar por isActive: true para mostrar solo las opciones disponibles.
Campos del Tipo de Actividad
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único del tipo de actividad |
name | string | Nombre descriptivo del tipo de actividad |
isActive | boolean | Indica si está activo y disponible para usar |
createdAt | datetime | Fecha de creación del registro |
Tipos de Actividad Comunes
- Llamada: Contacto directo por voz con el cliente
- Correo Electrónico: Envío de información o cotizaciones por email
- WhatsApp: Mensajería instantánea y compartir multimedia
- Otros
GET /external/catalogs/activity-types
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Activity types retrieved successfully",
"data": [
{
"id": 1,
"name": "Llamada",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
},
{
"id": 2,
"name": "Correo Electrónico",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
},
{
"id": 4,
"name": "WhatsApp",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No hay tipos de actividad configurados en el sistema (catálogo vacío) |
500 | Internal Server Error | Error interno del servidor al procesar la solicitud |
Nota: Si no hay tipos de actividad configurados, la respuesta es 404 (no 200) con data: null y el mensaje "No activity types found".
{
"succeeded": false,
"message": "No activity types found",
"data": null,
"statusCode": 404
}/external/catalogs/interest-levels Niveles de Interés
Obtiene el catálogo de niveles de interés disponibles para clasificar y priorizar clientes potenciales según su grado de intención de compra.
Nota: La respuesta incluye todos los niveles de interés configurados. Se recomienda filtrar por isActive: true para clasificar correctamente a los clientes.
Campos del Nivel de Interés
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único del nivel de interés |
name | string | Nombre descriptivo del nivel de interés |
isActive | boolean | Indica si está activo y disponible para usar |
createdAt | datetime | Fecha de creación del registro |
Niveles de Interés Comunes
- Alto: Cliente muy interesado, evaluación avanzada
- Medio: Cliente interesado, comparando opciones
- Bajo: Cliente en fase exploratoria inicial
Recomendación: Los niveles de interés son configurables según las necesidades de cada empresa. Utilízalos para clasificar y priorizar el seguimiento de clientes de acuerdo con tu estrategia de ventas.
GET /external/catalogs/interest-levels
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Interest levels retrieved successfully",
"data": [
{
"id": 1,
"name": "Alto",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
},
{
"id": 2,
"name": "Medio",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
},
{
"id": 3,
"name": "Bajo",
"isActive": true,
"createdAt": "2025-03-15T10:30:00"
}
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No hay niveles de interés configurados en el sistema (catálogo vacío) |
500 | Internal Server Error | Error interno del servidor al procesar la solicitud |
Nota: Si no hay niveles de interés configurados, la respuesta es 404 (no 200) con data: null y el mensaje "No interest levels found".
{
"succeeded": false,
"message": "No interest levels found",
"data": null,
"statusCode": 404
}/external/catalogs/financing-types?includeConditionalPayments={true|false} Tipos de Financiamiento
Obtiene el catálogo de opciones de financiamiento disponibles para informar a los clientes sobre las modalidades de pago, con opción de incluir cuotas personalizadas (condiciones de pago).
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
includeConditionalPayments | boolean | No | Incluir condiciones de pago personalizadas (por defecto: false) |
Nota: La respuesta incluye todos los tipos de financiamiento configurados, tanto activos como inactivos. Se recomienda filtrar por isActive: true para mostrar solo las opciones disponibles.
GET /external/catalogs/financing-types?includeConditionalPayments={true|false}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Financing types retrieved successfully",
"data": [
{
"id": 1,
"name": "Financiado",
"requiresBankApproval": false,
"isActive": true,
"createdAt": "2024-07-19T05:04:07.273",
"conditionalPayments": []
},
{
"id": 3,
"name": "Contado",
"requiresBankApproval": false,
"isActive": true,
"createdAt": "2025-01-05T22:42:34.05",
"conditionalPayments": []
},
{
"id": 4,
"name": "Hipotecario",
"requiresBankApproval": true,
"isActive": false,
"createdAt": "2025-01-05T22:42:44.863",
"conditionalPayments": []
}
],
"statusCode": 200
}{
"succeeded": true,
"message": "Financing types retrieved successfully",
"data": [
{
"id": 1,
"name": "Financiado",
"requiresBankApproval": false,
"isActive": true,
"createdAt": "2024-07-19T05:04:07.273",
"conditionalPayments": [
{
"code": "CUOTA_BALON",
"name": "C. Balón 1",
"currency": "PEN",
"amount": 3000.00,
"validFrom": "2025-09-03T00:00:00",
"validTo": null,
"isActive": true,
"createdAt": "2025-09-03T01:35:06.533"
},
{
"code": "CUOTA_BUEN_PAGADOR",
"name": "B. Pagador",
"currency": "PEN",
"amount": 3000.00,
"validFrom": "2025-09-03T00:00:00",
"validTo": null,
"isActive": true,
"createdAt": "2025-09-03T01:35:18.847"
}
]
},
{
"id": 3,
"name": "Contado",
"requiresBankApproval": false,
"isActive": true,
"createdAt": "2025-01-05T22:42:34.05",
"conditionalPayments": []
},
{
"id": 6,
"name": "Techo Propio",
"requiresBankApproval": false,
"isActive": true,
"createdAt": "2025-09-07T02:21:26.493",
"conditionalPayments": []
}
],
"statusCode": 200
}Estructura de Datos
Tipo de Financiamiento
| Campo | Tipo | Descripción |
|---|---|---|
id | integer | ID único del tipo de financiamiento |
name | string | Nombre descriptivo del tipo de financiamiento |
requiresBankApproval | boolean | Indica si requiere aprobación bancaria |
isActive | boolean | Indica si está activo y disponible para usar |
createdAt | datetime | Fecha de creación del tipo de financiamiento |
conditionalPayments | array | Lista de condiciones de pago personalizadas (vacío si includeConditionalPayments=false) |
Condición de Pago (ConditionalPayment)
| Campo | Tipo | Descripción |
|---|---|---|
code | string | Código único de la condición de pago |
name | string | Nombre corto de la condición |
currency | string | Moneda de la condición (PEN/USD) |
amount | decimal | Monto de la condición de pago |
validFrom | datetime | Fecha de inicio de vigencia |
validTo | datetime | Fecha de fin de vigencia (null si no tiene fecha de expiración) |
isActive | boolean | Indica si la condición está activa |
createdAt | datetime | Fecha de creación de la condición |
Tipos de Financiamiento Comunes
- Contado: Pago total al momento de la compra, sin financiamiento
- Financiado: Financiamiento directo con la inmobiliaria, sin banco intermediario
- Hipotecario: Crédito hipotecario con entidad bancaria, requiere aprobación
- Techo Propio: Programa estatal de vivienda para sectores de bajos recursos
- Fondo MIVIVIENDA: Fondo estatal que facilita el acceso a crédito hipotecario
Condiciones de Pago Personalizadas
Las condiciones de pago son cuotas especiales que pueden aplicarse a ciertos tipos de financiamiento:
- Cuota Balón: Pago único de un monto mayor en una fecha específica del cronograma
- Bono Buen Pagador: Descuento o beneficio por cumplir con los pagos puntualmente
Importante: Las condiciones de pago solo se incluyen cuando includeConditionalPayments=true. Por defecto, el array conditionalPayments estará vacío para todos los tipos de financiamiento.
Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Si no hay tipos de financiamiento configurados, la respuesta es igualmente exitosa (200) con el array data vacío y mensaje "No financing types found".
{
"succeeded": true,
"message": "No financing types found",
"data": [],
"statusCode": 200
}/external/catalogs/financing-parameters Parámetros de Financiamiento
Obtiene todos los parámetros de financiamiento disponibles en el sistema, organizados por proyecto inmobiliario.
Nota: La respuesta incluye tanto parámetros de financiamiento activos como inactivos. Use el campo isActive para filtrar solo el financiamiento vigente.
GET /external/catalogs/financing-parameters
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Financing parameters retrieved successfully",
"data": [
{
"projectCode": "VERDE2024",
"projectName": "RESIDENCIAL VERDE",
"reservationAmount": 500.00,
"initialFeePercent": 10.00,
"financedBalancePercent": 90.00,
"isActive": true,
"createdAt": "2024-01-15T10:30:00"
},
{
"projectCode": "MIRADOR2024",
"projectName": "MIRADOR DEL SUR",
"reservationAmount": 750.00,
"initialFeePercent": 15.00,
"financedBalancePercent": 85.00,
"isActive": false,
"createdAt": "2024-03-20T14:15:00"
},
{
"projectCode": "JARDIN2023",
"projectName": "CIUDAD JARDÍN",
"reservationAmount": 300.00,
"initialFeePercent": 20.00,
"financedBalancePercent": 80.00,
"isActive": false,
"createdAt": "2023-11-08T09:45:00"
},
{
"projectCode": "PLAZA2022",
"projectName": "PLAZA CENTRAL",
"reservationAmount": 1000.00,
"initialFeePercent": 25.00,
"financedBalancePercent": 75.00,
"isActive": false,
"createdAt": "2022-06-12T16:20:00"
}
],
"statusCode": 200
}Estructura de Parámetros de Financiamiento
| Campo | Tipo | Descripción |
|---|---|---|
projectCode | string | Código único del proyecto inmobiliario |
projectName | string | Nombre descriptivo del proyecto |
reservationAmount | decimal | Monto fijo de reserva requerido para separar una unidad |
initialFeePercent | decimal | Porcentaje de cuota inicial sobre el precio de venta |
financedBalancePercent | decimal | Porcentaje del saldo que puede ser financiado |
isActive | boolean | Indica si está activo |
createdAt | datetime | Fecha de creación de la configuración de parámetros |
Casos de Uso
1. Calcular Plan de Pagos
Utilice los parámetros para calcular automáticamente el desglose de pagos de una unidad:
- Precio de venta: S/ 50,000
- Parámetros del proyecto:
initialFeePercent = 10%,reservationAmount = 500 - Cuota inicial bruta: S/ 5,000
- Menos reserva: S/ 500
- Cuota inicial neta: S/ 4,500
- Monto a financiar: S/ 45,000 (90%)
Importante: Los parámetros son específicos por proyecto. Asegúrese de usar los parámetros correctos del proyecto al que pertenece la unidad que está cotizando.
Códigos de Respuesta
| Código | Mensaje | Descripción |
|---|---|---|
200 | OK | Parámetros obtenidos exitosamente |
401 | Unauthorized | Token de autenticación inválido o expirado |
Nota: Si no hay parámetros de financiamiento configurados en el sistema, la respuesta es exitosa (200) pero el array data está vacío.
{
"succeeded": true,
"message": "No financing parameters found.",
"data": [],
"statusCode": 200
}/external/catalogs/reversal-reason-codes Consultar Códigos de Motivo de Extorno
Devuelve el listado de códigos de motivo disponibles para registrar un extorno de pago.
GET /external/catalogs/reversal-reason-codes
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}{
"succeeded": true,
"message": "Reversal reason codes retrieved successfully",
"data": [
{ "code": "ADMIN_ERROR", "description": "Error administrativo" },
{ "code": "AMOUNT_ERROR", "description": "Error en el monto registrado" },
{ "code": "CLIENT_REQUEST", "description": "Solicitud del cliente" },
{ "code": "DUPLICATE_PAYMENT", "description": "Pago duplicado" },
{ "code": "FRAUD_DETECTED", "description": "Fraude detectado" },
{ "code": "INSUFFICIENT_FUNDS", "description": "Fondos insuficientes" },
{ "code": "METHOD_ERROR", "description": "Error en el método de pago" },
{ "code": "OPERATION_CANCELLED", "description": "Cancelación de operación" },
{ "code": "OTHER", "description": "Otros motivos" },
{ "code": "SYSTEM_ERROR", "description": "Error del sistema" }
],
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No hay códigos de motivo de extorno configurados en el sistema (catálogo vacío) |
500 | Internal Server Error | Error interno del servidor al procesar la solicitud |
Nota: Si no hay códigos de motivo de extorno configurados (o activos), la respuesta es 404 con data: null y el mensaje "No reversal reason codes found".
{
"succeeded": false,
"message": "No reversal reason codes found",
"data": null,
"statusCode": 404
}/external/contracts/sales/{saleId} Contratos Vinculados a una Venta
Obtiene la lista de contratos asociados a una venta específica mediante su ID.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
saleId | integer | Sí | Identificador único de la venta |
Nota: Cuando la venta existe pero aún no tiene contratos vinculados, la respuesta es exitosa (200) con el array contracts vacío.
Estructura de la Respuesta
| Campo Raíz | Tipo | Descripción |
|---|---|---|
succeeded | boolean | Indica si la operación fue exitosa |
message | string | Mensaje descriptivo del resultado |
data | object | null | Objeto con la venta y sus contratos, o null si la venta no existe |
statusCode | integer | Código de estado HTTP |
Objeto data
proformaId: ID único de la venta consultadacorrelative: Correlativo del documentocontracts: Array de contratos vinculados a la venta
Objeto contracts[]
| Campo | Tipo | Descripción |
|---|---|---|
contractId | integer | ID interno del contrato, usado para consultar su URL de descarga |
contractStatus | string | Estado actual del contrato en el flujo de firma electrónica (ver tabla de estados) |
GET /external/contracts/sales/{saleId}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}GET /external/contracts/sales/123
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Subdomain: miempresa{
"succeeded": true,
"message": "Sale contracts retrieved successfully",
"data": {
"proformaId": 123,
"correlative": "202512-000000020",
"contracts": [
{
"contractId": 6,
"contractStatus": "draft"
}
]
},
"statusCode": 200
}{
"succeeded": true,
"message": "Sale contracts retrieved successfully",
"data": {
"proformaId": 59,
"correlative": "202512-000000026",
"contracts": []
},
"statusCode": 200
}Estados del Contrato
El campo contractStatus refleja la etapa actual del contrato dentro del flujo de firma electrónica.
| Estado | Descripción |
|---|---|
draft | Valor por defecto al crear el contrato localmente, aún no enviado al proveedor de firmas (Keynua) |
created | Contrato creado en el proveedor de firmas |
in_progress | Proceso de firma iniciado; el contrato está listo o en curso de ser firmado por los participantes |
signed | Contrato firmado exitosamente por todos los participantes |
error | Ocurrió un error en algún elemento del proceso de firma |
deleted | Contrato eliminado en el proveedor de firmas |
Flujo típico: draft → created → in_progress → signed
Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | saleId no es un entero válido |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No existe una venta con el ID indicado |
{
"succeeded": false,
"message": "No sale found with id 592",
"data": null,
"statusCode": 404
}/external/contracts/{contractId}/download-url URL de Descarga de Contrato
Genera una URL temporal firmada para descargar el archivo PDF de un contrato específico a partir de su ID interno.
contractId desde el endpoint de contratos por venta, este endpoint genera un enlace de descarga seguro con expiración automática. Por defecto la URL es válida durante 1 hora desde su generación, pero este tiempo puede ajustarse con el parámetro expirationMinutes.
Parámetros de Ruta
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
contractId | integer | Sí | ID interno del contrato, obtenido desde GET /external/contracts/sales/{saleId} |
Parámetros Query
| Parámetro | Tipo | Requerido | Descripción |
|---|---|---|---|
expirationMinutes | integer | No | Minutos de vigencia de la URL firmada (por defecto: 60) |
Estructura de la Respuesta
| Campo Raíz | Tipo | Descripción |
|---|---|---|
succeeded | boolean | Indica si la operación fue exitosa |
message | string | Mensaje descriptivo del resultado |
data | object | null | Objeto con la URL generada, o null si el contrato no existe |
statusCode | integer | Código de estado HTTP |
Objeto data
| Campo | Tipo | Descripción |
|---|---|---|
contractId | integer | ID interno del contrato consultado |
url | string | URL firmada para descargar el PDF del contrato |
expiresAt | string (ISO 8601 UTC) | Fecha y hora exacta en que expira la URL generada |
Expiración: Por defecto la URL generada tiene una vigencia de 60 minutos (3600 segundos) desde su emisión, pero este valor puede modificarse enviando el parámetro expirationMinutes en la solicitud. Pasado ese tiempo el enlace deja de ser válido y se deberá solicitar una nueva URL llamando nuevamente a este endpoint.
Disponibilidad: La URL de descarga solo estará disponible para contratos en estado signed. Para contratos en otros estados como draft, created, in_progress, error o deleted, el archivo firmado aún no existe o no es accesible.
GET /external/contracts/{contractId}/download-url?expirationMinutes={expirationMinutes}
Headers:
Authorization: Bearer {accessToken}
X-Subdomain: {su-subdomain}GET /external/contracts/6/download-url?expirationMinutes=120
Headers:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
X-Subdomain: miempresa{
"succeeded": true,
"message": "Contract download URL generated successfully",
"data": {
"contractId": 1,
"url": "https://cdn.logicwareperu.com/signatures/files/.../signed_20260319_090620.pdf?X-Cdn-Algorithm=HMACSHA256&X-Cdn-Date=20260321T084313Z&X-Cdn-Expires=3600&X-Cdn-Nonce=...&X-Cdn-Signature=...",
"expiresAt": "2026-03-21T09:43:13.637322Z"
},
"statusCode": 200
}Códigos de Error Comunes
| Código | Mensaje | Descripción |
|---|---|---|
400 | Bad Request | contractId no es un entero válido |
401 | Unauthorized | Token de autenticación inválido o expirado |
404 | Not Found | No existe un contrato con el ID indicado |
Flujo recomendado: Primero consulta GET /external/contracts/sales/{saleId} para obtener el contractId y verificar que el contrato existe y está en estado signed. Luego llama a este endpoint para obtener la URL de descarga.
{
"succeeded": false,
"message": "Contract with id 222 not found",
"data": null,
"statusCode": 404
}Códigos de Error Comunes
Estos códigos de error aplican a todos los endpoints de la API:
| Código | Descripción | Acción recomendada |
|---|---|---|
| 400 | Parámetros inválidos | Verificar parámetros enviados |
| 401 | Token inválido o expirado | Renovar token de autenticación |
| 403 | Sin permisos para el recurso | Verificar accesos con Logicware |
| 404 | Recurso no encontrado | Verificar códigos e IDs enviados |
| 429 | Rate limit excedido | Implementar throttling y reintentos |
| 500 | Error interno del servidor | Reintentar más tarde o contactar soporte |
| 503 | Servicio no disponible | Sistema en mantenimiento, reintentar después |
Manuales
Guías funcionales
- Gestión de Leads y Segmentación
- Oportunidades, Proformas y Ventas
- Posventa y SLA
- Conexión WhatsApp con Logicware
Guías técnicas
- Instalación on‑premise / IIS
- Gateway con YARP
- Observabilidad (TraceId / OpenTelemetry)
Conexión WhatsApp con Logicware
Introducción
Este manual explica el procedimiento para activar y mantener la conexión de WhatsApp con el CRM Logicware. El uso correcto asegura que los mensajes y clientes que lleguen a WhatsApp se sincronicen automáticamente con el sistema y puedan enviar plantillas.
Icono de Estado
En la parte superior derecha del CRM hay un ícono de WhatsApp que indica el estado:
- Verde: Conectado
- Naranja/Rojo: Desconectado
- Gris: Estado desconocido
Procedimiento de Conexión
- Haz clic en el ícono de WhatsApp en la parte superior.
- Se abrirá una ventana emergente con un código QR.
- En tu WhatsApp móvil entra a Dispositivos vinculados.
- Escanea el código QR mostrado.
- Recarga la página del CRM para validar el estado de la conexión.
Recomendaciones
- Verifica diariamente el estado de conexión de WhatsApp.
- Si hay problemas frecuentes, contacta soporte (953 448 476).
- Mantén actualizada la aplicación de WhatsApp.
- No compartas tu acceso de WhatsApp corporativo.
Sugerencia
Revisa periódicamente el estado de tu conexión de WhatsApp. Si está inactiva o desconectada, repite el procedimiento de conexión para mantener la sincronización óptima.
Problemas comunes y solución
Si no aparece el código QR al intentar vincular tu cuenta:
- Ir a Configuración > Integraciones.
- Seleccionar Conexión WhatsApp.
- Dar clic en Agregar Instancia.
- Ingresar datos proporcionados por Logicware (Instancia, URL servicio, Token).
- Si no cuentas con credenciales, solicítalas a Logicware.
- Guardar cambios e intentar nuevamente.
APIs
Documentación de endpoints REST, autenticación (OAuth2/JWT), límites de uso y ejemplos de respuesta. Próximamente publicaremos especificaciones OpenAPI (Swagger) descargables.
SDKs / Ejemplos
Introducción
No publicamos un SDK oficial por lenguaje: la API de Proveedores es REST/JSON estándar, así que puedes integrarla con el cliente HTTP que prefieras. Abajo tienes ejemplos completos y funcionales en los lenguajes más usados para los flujos más comunes: autenticación, consulta de stock, creación de leads y recepción/verificación de webhooks.
- cURL — para pruebas rápidas desde terminal
- JavaScript (Node.js) — usando
fetchnativo - Python — usando
requests - C# (.NET) — usando
HttpClient - PHP — usando la extensión
cURL - Java — usando
java.net.http.HttpClient(Java 11+)
Todos los ejemplos asumen la URL base https://gw.logicwareperu.com. Reemplaza {tu-api-key}, {tu-subdominio} y demás valores entre llaves por los tuyos.
1. Autenticación — Generar Token
Primer paso obligatorio de cualquier integración: intercambiar tu API Key por un token Bearer (JWT), válido por 1 hora por defecto. Ver Generar Token para el detalle completo del endpoint.
curl -X POST "https://gw.logicwareperu.com/auth/external/token" \
-H "X-API-Key: {tu-api-key}" \
-H "X-Subdomain: {tu-subdominio}" \
-H "Accept: application/json"const response = await fetch("https://gw.logicwareperu.com/auth/external/token", {
method: "POST",
headers: {
"X-API-Key": process.env.LW_API_KEY,
"X-Subdomain": process.env.LW_SUBDOMAIN,
"Accept": "application/json"
}
});
const body = await response.json();
const accessToken = body.data.accessToken;
const expiresAt = body.data.expiresAt;
console.log("Token válido hasta:", expiresAt);import os
import requests
response = requests.post(
"https://gw.logicwareperu.com/auth/external/token",
headers={
"X-API-Key": os.environ["LW_API_KEY"],
"X-Subdomain": os.environ["LW_SUBDOMAIN"],
"Accept": "application/json",
},
)
response.raise_for_status()
data = response.json()["data"]
access_token = data["accessToken"]
print("Token válido hasta:", data["expiresAt"])using var client = new HttpClient { BaseAddress = new Uri("https://gw.logicwareperu.com") };
client.DefaultRequestHeaders.Add("X-API-Key", apiKey);
client.DefaultRequestHeaders.Add("X-Subdomain", subdomain);
client.DefaultRequestHeaders.Add("Accept", "application/json");
var response = await client.PostAsync("/auth/external/token", content: null);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<TokenResponse>();
var accessToken = body.Data.AccessToken;
Console.WriteLine("Token válido hasta: " + body.Data.ExpiresAt);<?php
$ch = curl_init("https://gw.logicwareperu.com/auth/external/token");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-API-Key: " . $apiKey,
"X-Subdomain: " . $subdomain,
"Accept: application/json",
],
]);
$response = json_decode(curl_exec($ch), true);
curl_close($ch);
$accessToken = $response["data"]["accessToken"];
echo "Token válido hasta: " . $response["data"]["expiresAt"];HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://gw.logicwareperu.com/auth/external/token"))
.header("X-API-Key", apiKey)
.header("X-Subdomain", subdomain)
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.noBody())
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = objectMapper.readTree(response.body()).get("data");
String accessToken = data.get("accessToken").asText();
System.out.println("Token válido hasta: " + data.get("expiresAt").asText());2. Consultar Stock de Unidades
Ejemplo de una llamada autenticada con parámetros query. Ver Stock para el detalle completo de la respuesta.
curl -X GET "https://gw.logicwareperu.com/external/units/stock?projectCode=LOGICWARE&stageId=1" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Subdomain: {tu-subdominio}"const url = "https://gw.logicwareperu.com/external/units/stock?projectCode=LOGICWARE&stageId=1";
const response = await fetch(url, {
headers: {
"Authorization": "Bearer " + accessToken,
"X-Subdomain": subdomain
}
});
const body = await response.json();
console.log("Total de unidades:", body.data.summary.total);
console.log(body.data.properties);response = requests.get(
"https://gw.logicwareperu.com/external/units/stock",
params={"projectCode": "LOGICWARE", "stageId": 1},
headers={
"Authorization": f"Bearer {access_token}",
"X-Subdomain": SUBDOMAIN,
},
)
response.raise_for_status()
data = response.json()["data"]
print("Total de unidades:", data["summary"]["total"])
for unit in data["properties"]:
print(unit["code"], unit["status"])client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync(
"/external/units/stock?projectCode=LOGICWARE&stageId=1");
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<Response<UnitStockData>>();
Console.WriteLine("Total de unidades: " + body.Data.Summary.Total);<?php
$query = http_build_query(["projectCode" => "LOGICWARE", "stageId" => 1]);
$ch = curl_init("https://gw.logicwareperu.com/external/units/stock?" . $query);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $accessToken,
"X-Subdomain: " . $subdomain,
],
]);
$data = json_decode(curl_exec($ch), true)["data"];
curl_close($ch);
echo "Total de unidades: " . $data["summary"]["total"];HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://gw.logicwareperu.com/external/units/stock?projectCode=LOGICWARE&stageId=1"))
.header("Authorization", "Bearer " + accessToken)
.header("X-Subdomain", subdomain)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = objectMapper.readTree(response.body()).get("data");
System.out.println("Total de unidades: " + data.get("summary").get("total").asInt());3. Crear un Lead
Ejemplo de una llamada POST con cuerpo JSON. Ver Crear Lead para la lista completa de campos y validaciones.
curl -X POST "https://gw.logicwareperu.com/external/leads/create" \
-H "Authorization: Bearer {accessToken}" \
-H "X-Subdomain: {tu-subdominio}" \
-H "Content-Type: application/json" \
-d '{
"portalCode": "LOGICWAREBOT",
"projectCode": "ALAMEDA2",
"documentType": 1,
"documentNumber": "12345678",
"firstName": "Carlos",
"paternalLastname": "Rodriguez",
"maternalLastname": "Lopez",
"email": "carlos.rodriguez@email.com",
"phoneNumber": "+51987654321",
"comments": "Interesado en departamentos de 3 dormitorios"
}'const payload = {
portalCode: "LOGICWAREBOT",
projectCode: "ALAMEDA2",
documentType: 1,
documentNumber: "12345678",
firstName: "Carlos",
paternalLastname: "Rodriguez",
maternalLastname: "Lopez",
email: "carlos.rodriguez@email.com",
phoneNumber: "+51987654321",
comments: "Interesado en departamentos de 3 dormitorios"
};
const response = await fetch("https://gw.logicwareperu.com/external/leads/create", {
method: "POST",
headers: {
"Authorization": "Bearer " + accessToken,
"X-Subdomain": subdomain,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
const body = await response.json();
console.log("Lead creado con id:", body.data.leadId);payload = {
"portalCode": "LOGICWAREBOT",
"projectCode": "ALAMEDA2",
"documentType": 1,
"documentNumber": "12345678",
"firstName": "Carlos",
"paternalLastname": "Rodriguez",
"maternalLastname": "Lopez",
"email": "carlos.rodriguez@email.com",
"phoneNumber": "+51987654321",
"comments": "Interesado en departamentos de 3 dormitorios",
}
response = requests.post(
"https://gw.logicwareperu.com/external/leads/create",
json=payload,
headers={
"Authorization": f"Bearer {access_token}",
"X-Subdomain": SUBDOMAIN,
},
)
response.raise_for_status()
data = response.json()["data"]
print("Lead creado con id:", data["leadId"])var payload = new
{
portalCode = "LOGICWAREBOT",
projectCode = "ALAMEDA2",
documentType = 1,
documentNumber = "12345678",
firstName = "Carlos",
paternalLastname = "Rodriguez",
maternalLastname = "Lopez",
email = "carlos.rodriguez@email.com",
phoneNumber = "+51987654321",
comments = "Interesado en departamentos de 3 dormitorios"
};
var response = await client.PostAsJsonAsync("/external/leads/create", payload);
response.EnsureSuccessStatusCode();
var body = await response.Content.ReadFromJsonAsync<Response<LeadCreatedData>>();
Console.WriteLine("Lead creado con id: " + body.Data.LeadId);<?php
$payload = [
"portalCode" => "LOGICWAREBOT",
"projectCode" => "ALAMEDA2",
"documentType" => 1,
"documentNumber" => "12345678",
"firstName" => "Carlos",
"paternalLastname" => "Rodriguez",
"maternalLastname" => "Lopez",
"email" => "carlos.rodriguez@email.com",
"phoneNumber" => "+51987654321",
"comments" => "Interesado en departamentos de 3 dormitorios",
];
$ch = curl_init("https://gw.logicwareperu.com/external/leads/create");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . $accessToken,
"X-Subdomain: " . $subdomain,
"Content-Type: application/json",
],
]);
$data = json_decode(curl_exec($ch), true)["data"];
curl_close($ch);
echo "Lead creado con id: " . $data["leadId"];String payload = objectMapper.writeValueAsString(Map.of(
"portalCode", "LOGICWAREBOT",
"projectCode", "ALAMEDA2",
"documentType", 1,
"documentNumber", "12345678",
"firstName", "Carlos",
"paternalLastname", "Rodriguez",
"email", "carlos.rodriguez@email.com",
"phoneNumber", "+51987654321"
));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://gw.logicwareperu.com/external/leads/create"))
.header("Authorization", "Bearer " + accessToken)
.header("X-Subdomain", subdomain)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(payload))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JsonNode data = objectMapper.readTree(response.body()).get("data");
System.out.println("Lead creado con id: " + data.get("leadId").asInt());Nota: si documentNumber ya existe para el proyecto, la API igual responde 201 pero con parentId apuntando al lead original — no lances un error en tu integración al ver 201, revisa data.parentId. Ver Crear Lead para el detalle.
4. Recibir y Verificar Webhooks
Servidor mínimo que recibe el POST del webhook, valida la firma X-Webhook-Signature (HMAC-SHA256 sobre el cuerpo crudo, formato sha256=<hash_hex>) en tiempo constante, y responde 200 rápido antes de procesar. Ver Webhooks para el detalle completo.
import express from "express";
import crypto from "crypto";
const app = express();
// Body crudo requerido para poder recalcular la firma exactamente igual
app.use(express.raw({ type: "application/json" }));
const WEBHOOK_SECRET = process.env.LW_WEBHOOK_SECRET;
app.post("/webhooks/logicware", (req, res) => {
const signature = req.header("X-Webhook-Signature") || "";
const expected = "sha256=" + crypto
.createHmac("sha256", WEBHOOK_SECRET)
.update(req.body)
.digest("hex");
const sigBuffer = Buffer.from(signature);
const expectedBuffer = Buffer.from(expected);
const isValid = sigBuffer.length === expectedBuffer.length &&
crypto.timingSafeEqual(sigBuffer, expectedBuffer);
if (!isValid) {
return res.status(401).send("invalid signature");
}
// Responde rápido, procesa el evento de forma asíncrona después
res.status(200).send("ok");
const event = JSON.parse(req.body);
console.log(event.eventType, event.messageId);
});
app.listen(3000);import hashlib
import hmac
import os
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["LW_WEBHOOK_SECRET"]
@app.post("/webhooks/logicware")
def receive_webhook():
raw_body = request.get_data()
signature = request.headers.get("X-Webhook-Signature", "")
expected = "sha256=" + hmac.new(
WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256
).hexdigest()
if not hmac.compare_digest(signature, expected):
abort(401)
event = request.get_json()
print(event["eventType"], event["messageId"])
return "ok", 200app.MapPost("/webhooks/logicware", async (HttpRequest request) =>
{
using var reader = new StreamReader(request.Body);
var rawBody = await reader.ReadToEndAsync();
var signature = request.Headers["X-Webhook-Signature"].ToString();
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(webhookSecret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody));
var expected = "sha256=" + Convert.ToHexString(hash).ToLowerInvariant();
var isValid = CryptographicOperations.FixedTimeEquals(
Encoding.UTF8.GetBytes(signature),
Encoding.UTF8.GetBytes(expected));
if (!isValid)
{
return Results.Unauthorized();
}
var evt = JsonSerializer.Deserialize<WebhookEvent>(rawBody);
Console.WriteLine(evt.EventType + " " + evt.MessageId);
return Results.Ok();
});<?php
$rawBody = file_get_contents("php://input");
$signature = $_SERVER["HTTP_X_WEBHOOK_SIGNATURE"] ?? "";
$expected = "sha256=" . hash_hmac("sha256", $rawBody, $webhookSecret);
if (!hash_equals($expected, $signature)) {
http_response_code(401);
exit("invalid signature");
}
http_response_code(200);
echo "ok";
$event = json_decode($rawBody, true);
error_log($event["eventType"] . " " . $event["messageId"]);@PostMapping("/webhooks/logicware")
public ResponseEntity<String> receiveWebhook(
@RequestBody String rawBody,
@RequestHeader("X-Webhook-Signature") String signature) throws Exception {
Mac hmac = Mac.getInstance("HmacSHA256");
hmac.init(new SecretKeySpec(webhookSecret.getBytes(), "HmacSHA256"));
String expected = "sha256=" + HexFormat.of().formatHex(hmac.doFinal(rawBody.getBytes()));
boolean isValid = MessageDigest.isEqual(
expected.getBytes(StandardCharsets.UTF_8),
signature.getBytes(StandardCharsets.UTF_8));
if (!isValid) {
return ResponseEntity.status(401).build();
}
JsonNode event = objectMapper.readTree(rawBody);
log.info("{} {}", event.get("eventType").asText(), event.get("messageId").asText());
return ResponseEntity.ok("ok");
}Importante: la firma se calcula sobre el cuerpo crudo (bytes exactos) de la petición, antes de cualquier parseo JSON. Si tu framework parsea el body automáticamente antes de tu handler (algo común en Express/Flask/Spring por defecto), asegúrate de capturar el raw body primero — de lo contrario el hash nunca va a coincidir.
Changelog
- 2025‑09‑30: Agregada documentación completa de API de Proveedores v2.1 con ejemplos en múltiples lenguajes.
- 2025‑09‑02: Nuevos eventos de separación/venta/devolución. Estructura base de payload.
- 2025‑01‑15: Firma HMAC‑SHA256 opcional por endpoint.
Soporte
Canales de contacto
Al reportar un problema incluye:
X-Request-IDde la solicitud fallida- Timestamp del error
- Código de estado HTTP recibido
- Mensaje de error completo
- Endpoint y parámetros utilizados (sin credenciales)
¿Necesitas ayuda con webhooks? Incluye también messageId y correlationId de tus eventos para acelerar el diagnóstico.
Seguridad
Todos los accesos deben realizarse por HTTPS. Si activas la firma de webhook, valida el HMAC con comparación en tiempo constante. Mantén listas de eventos soportados y registra auditoría básica (messageId, correlationId, resultado).
Recomendaciones adicionales:
- Usa variables de entorno para credenciales
- Implementa rotación de API Keys cada 90 días
- Audita logs de acceso regularmente
- Nunca expongas credenciales en código cliente
- Valida siempre certificados SSL/TLS
Asistente Virtual de IA v2.0
Resumen
El Asistente Virtual de IA de Logicware es un widget conversacional para tu web que responde consultas de disponibilidad, precios, promociones y financiamiento, y además capta leads en tu CRM.
🤖 Conversacional
NLP y contexto para flujos inmobiliarios comunes.
⚡ Rápido
Carga asíncrona, bajo peso <100KB y sin bloquear tu sitio.
📈 Enfocado en ventas
Deriva a asesor y registra lead en tu CRM.
Introducción
El Asistente de IA responde en lenguaje natural sobre proyectos, lotes/departamentos, precios, promociones y financiamiento. Solicita nombre y celular para registrar el lead en tu CRM y, cuando corresponde, deriva a un asesor humano.
- Consulta inventario comercial en tiempo real.
- Mantiene el contexto de la conversación.
- 100% responsive y seguro (HTTPS).
Instalación del widget
Inserta el siguiente script antes de </body> en tu sitio.
<!-- Asistente Virtual IA Logicware -->
<script src="https://widgets.logicwareperu.com/widget/tu_cliente_id.js"></script> WordPress (rápido)
- Instala “Insert Headers and Footers”.
- Pega el script en Scripts in Footer y guarda.
HTML estático
<!DOCTYPE html>
<html lang="es">
<head>...</head>
<body>
<!-- Tu contenido -->
<!-- Asistente Virtual IA Logicware -->
<script src="https://widgets.logicwareperu.com/widget/tu_cliente_id.js"></script>
</body>
</html> Verificación
- Limpia caché (Ctrl/Cmd + Shift + Supr) e ingresa en incógnito.
- Abre tu web y confirma el botón flotante del asistente.
- Haz clic y verifica: mensaje de bienvenida y atajos (Disponibilidad, Precios, Promos, Visitas).
Si no aparece: verifica que el script esté antes de </body>, el ID sea correcto y no haya extensiones bloqueando scripts. Revisa la Consola (F12) y comparte una captura a soporte.
Personalización
Podemos ajustar apariencia y textos del asistente a tu marca.
- Colores/Logo: envía paleta (hex) y logotipo.
- Bienvenida: texto por proyecto/ciudad (opcional).
- Atajos: Disponibilidad, Precios, Promociones, Visitas, Financiamiento.
FAQ & Soporte
¿Responde en tiempo real?
Sí, consulta inventario comercial en el momento de la pregunta.
¿Qué datos mínimos solicita?
Nombre y celular. Correo y preferencia de contacto son opcionales.
¿Afecta el rendimiento?
No. Carga asíncrona y tamaño ligero (<100KB).
¿Funciona en móviles?
100% responsive.
Soporte
📧 soporte@logicwareperu.com
💬 WhatsApp: +51 925 634 994