n8n-nodes-aivencerealtycrm 2.0.4 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -68,6 +68,38 @@ class AivenceRealty {
68
68
  name: 'Agent',
69
69
  value: 'agent',
70
70
  },
71
+ // ═══════ SPRINT DOCUMENTOS ═══════
72
+ {
73
+ name: 'Owner Expense',
74
+ value: 'ownerExpense',
75
+ description: 'Gastos de propietarios',
76
+ },
77
+ {
78
+ name: 'Contractor Invoice',
79
+ value: 'contractorInvoice',
80
+ description: 'Facturas de contratistas',
81
+ },
82
+ // ═══════ V2.3.0 - AUTOMATION & POLICIES ═══════
83
+ {
84
+ name: 'Payment Proof',
85
+ value: 'paymentProof',
86
+ description: 'Procesamiento de comprobantes de pago',
87
+ },
88
+ {
89
+ name: 'Automation',
90
+ value: 'automation',
91
+ description: 'Registro de ejecuciones y logs',
92
+ },
93
+ {
94
+ name: 'Policy',
95
+ value: 'policy',
96
+ description: 'Evaluación de políticas y aprobaciones',
97
+ },
98
+ {
99
+ name: 'Notification',
100
+ value: 'notification',
101
+ description: 'Envío de notificaciones multi-canal',
102
+ },
71
103
  ],
72
104
  default: 'lead',
73
105
  description: 'Recurso del CRM a operar',
@@ -1481,6 +1513,12 @@ class AivenceRealty {
1481
1513
  description: 'Crear inquilino',
1482
1514
  action: 'Crear inquilino',
1483
1515
  },
1516
+ {
1517
+ name: 'Validate Exists',
1518
+ value: 'validateExists',
1519
+ description: 'Validar si el inquilino existe',
1520
+ action: 'Validar inquilino',
1521
+ },
1484
1522
  ],
1485
1523
  default: 'list',
1486
1524
  },
@@ -1493,11 +1531,11 @@ class AivenceRealty {
1493
1531
  displayOptions: {
1494
1532
  show: {
1495
1533
  resource: ['tenant'],
1496
- operation: ['get'],
1534
+ operation: ['get', 'validateExists'],
1497
1535
  },
1498
1536
  },
1499
1537
  default: '',
1500
- description: 'ID del inquilino',
1538
+ description: 'ID del inquilino a consultar o validar',
1501
1539
  },
1502
1540
  // Tenant Create: JSON from previous node
1503
1541
  {
@@ -2461,249 +2499,1174 @@ class AivenceRealty {
2461
2499
  placeholder: 'Palermo',
2462
2500
  description: 'Filtrar agentes por zona (opcional)',
2463
2501
  },
2464
- ],
2465
- };
2466
- }
2467
- async execute() {
2468
- const items = this.getInputData();
2469
- const returnData = [];
2470
- const credentials = await this.getCredentials('aivenceRealtyApi');
2471
- const baseUrl = credentials.baseUrl;
2472
- for (let i = 0; i < items.length; i++) {
2473
- try {
2474
- const resource = this.getNodeParameter('resource', i);
2475
- const operation = this.getNodeParameter('operation', i);
2476
- let responseData;
2477
- // ============================================
2478
- // LEAD RESOURCE
2479
- // ============================================
2480
- if (resource === 'lead') {
2481
- if (operation === 'list') {
2482
- responseData = await this.helpers.httpRequest({
2483
- method: 'GET',
2484
- url: `${baseUrl}/api/v1/leads`,
2485
- json: true,
2486
- });
2487
- }
2488
- else if (operation === 'create') {
2489
- const useJsonInput = this.getNodeParameter('useJsonInput', i, false);
2490
- let body = {};
2491
- if (useJsonInput) {
2492
- // Usar JSON del nodo anterior
2493
- body = items[i].json;
2494
- }
2495
- else {
2496
- // Construir body con campos estructurados
2497
- body = {
2498
- name: this.getNodeParameter('name', i),
2499
- email: this.getNodeParameter('email', i),
2500
- phone: this.getNodeParameter('phone', i),
2501
- message: this.getNodeParameter('message', i, ''),
2502
- interest: this.getNodeParameter('interest', i, 'compra'),
2503
- budget_min: this.getNodeParameter('budget_min', i, 0),
2504
- budget_max: this.getNodeParameter('budget_max', i, 0),
2505
- property_type: this.getNodeParameter('property_type', i, 'apartamento'),
2506
- conversation_id: this.getNodeParameter('conversation_id', i, ''),
2507
- status: this.getNodeParameter('status', i, 'new'),
2508
- score: this.getNodeParameter('score', i, 0),
2509
- category: this.getNodeParameter('category', i, 'warm'),
2510
- notes: this.getNodeParameter('notes', i, ''),
2511
- crm_id: this.getNodeParameter('crm_id', i, ''),
2512
- };
2513
- // Procesar preferred_areas (convertir string separado por comas a array)
2514
- const areasString = this.getNodeParameter('preferred_areas', i, '');
2515
- if (areasString) {
2516
- body.preferred_areas = areasString.split(',').map((area) => area.trim());
2517
- }
2518
- // Procesar tags (convertir string separado por comas a array)
2519
- const tagsString = this.getNodeParameter('tags', i, '');
2520
- if (tagsString) {
2521
- body.tags = tagsString.split(',').map((tag) => tag.trim());
2522
- }
2523
- // Remover campos vacíos opcionales
2524
- if (!body.message)
2525
- delete body.message;
2526
- if (body.budget_min === 0)
2527
- delete body.budget_min;
2528
- if (body.budget_max === 0)
2529
- delete body.budget_max;
2530
- if (!body.preferred_areas || body.preferred_areas.length === 0)
2531
- delete body.preferred_areas;
2532
- if (!body.conversation_id)
2533
- delete body.conversation_id;
2534
- if (!body.notes)
2535
- delete body.notes;
2536
- if (!body.crm_id)
2537
- delete body.crm_id;
2538
- if (!body.tags || body.tags.length === 0)
2539
- delete body.tags;
2540
- if (body.score === 0)
2541
- delete body.score;
2542
- }
2543
- responseData = await this.helpers.httpRequest({
2544
- method: 'POST',
2545
- url: `${baseUrl}/api/v1/leads`,
2546
- body,
2547
- json: true,
2548
- });
2549
- }
2550
- else if (operation === 'update') {
2551
- const leadId = this.getNodeParameter('leadId', i);
2552
- const useJsonInput = this.getNodeParameter('useJsonInput', i, false);
2553
- let body = {};
2554
- if (useJsonInput) {
2555
- // Usar JSON del nodo anterior
2556
- body = items[i].json;
2557
- }
2558
- else {
2559
- // Construir body con campos estructurados (solo enviar campos proporcionados)
2560
- const name = this.getNodeParameter('name', i, '');
2561
- const email = this.getNodeParameter('email', i, '');
2562
- const phone = this.getNodeParameter('phone', i, '');
2563
- const message = this.getNodeParameter('message', i, '');
2564
- const interest = this.getNodeParameter('interest', i, '');
2565
- const budget_min = this.getNodeParameter('budget_min', i, 0);
2566
- const budget_max = this.getNodeParameter('budget_max', i, 0);
2567
- const property_type = this.getNodeParameter('property_type', i, '');
2568
- const areasString = this.getNodeParameter('preferred_areas', i, '');
2569
- const conversation_id = this.getNodeParameter('conversation_id', i, '');
2570
- const status = this.getNodeParameter('status', i, '');
2571
- const score = this.getNodeParameter('score', i, 0);
2572
- const category = this.getNodeParameter('category', i, '');
2573
- const notes = this.getNodeParameter('notes', i, '');
2574
- const crm_id = this.getNodeParameter('crm_id', i, '');
2575
- const tagsString = this.getNodeParameter('tags', i, '');
2576
- if (name)
2577
- body.name = name;
2578
- if (email)
2579
- body.email = email;
2580
- if (phone)
2581
- body.phone = phone;
2582
- if (message)
2583
- body.message = message;
2584
- if (interest)
2585
- body.interest = interest;
2586
- if (budget_min > 0)
2587
- body.budget_min = budget_min;
2588
- if (budget_max > 0)
2589
- body.budget_max = budget_max;
2590
- if (property_type)
2591
- body.property_type = property_type;
2592
- if (conversation_id)
2593
- body.conversation_id = conversation_id;
2594
- if (status)
2595
- body.status = status;
2596
- if (score > 0)
2597
- body.score = score;
2598
- if (category)
2599
- body.category = category;
2600
- if (notes)
2601
- body.notes = notes;
2602
- if (crm_id)
2603
- body.crm_id = crm_id;
2604
- if (areasString) {
2605
- body.preferred_areas = areasString.split(',').map((area) => area.trim());
2606
- }
2607
- if (tagsString) {
2608
- body.tags = tagsString.split(',').map((tag) => tag.trim());
2609
- }
2610
- }
2611
- responseData = await this.helpers.httpRequest({
2612
- method: 'PATCH',
2613
- url: `${baseUrl}/api/v1/leads/${leadId}`,
2614
- body,
2615
- json: true,
2616
- });
2617
- }
2618
- else if (operation === 'get') {
2619
- const leadId = this.getNodeParameter('leadId', i);
2620
- responseData = await this.helpers.httpRequest({
2621
- method: 'GET',
2622
- url: `${baseUrl}/api/v1/leads/${leadId}`,
2623
- json: true,
2624
- });
2625
- }
2626
- else if (operation === 'addActivity') {
2627
- const leadId = this.getNodeParameter('leadId', i);
2628
- const activityType = this.getNodeParameter('activity_type', i);
2629
- const description = this.getNodeParameter('activity_description', i);
2630
- const body = {
2631
- activity_type: activityType,
2632
- description: description,
2633
- };
2634
- responseData = await this.helpers.httpRequest({
2635
- method: 'POST',
2636
- url: `${baseUrl}/api/v1/mariana/leads/${leadId}/activity`,
2637
- body,
2638
- json: true,
2639
- });
2640
- }
2641
- }
2642
2502
  // ============================================
2643
- // PROPERTY RESOURCE
2503
+ // OWNER EXPENSE OPERATIONS
2644
2504
  // ============================================
2645
- else if (resource === 'property') {
2646
- if (operation === 'search') {
2647
- // ============================================
2648
- // PROPERTY SEARCH - Smart Zone Logic compatible
2649
- // ============================================
2650
- const qs = {};
2651
- // Property ID (búsqueda directa por ID)
2652
- const propertyId = this.getNodeParameter('searchPropertyId', i, 0);
2653
- if (propertyId > 0) {
2654
- qs.id = propertyId;
2655
- }
2656
- // Operación -> Estado (Laravel espera "estado": "for_rent" o "for_sale")
2657
- const operacion = this.getNodeParameter('searchOperacion', i, '');
2658
- if (operacion && operacion !== 'NULL' && operacion !== '') {
2659
- qs.estado = operacion;
2660
- }
2661
- // Tipo de Propiedad
2662
- const tipoPropiedad = this.getNodeParameter('searchTipoPropiedad', i, '');
2663
- if (tipoPropiedad && tipoPropiedad !== '' && tipoPropiedad !== 'NULL') {
2664
- qs.tipo_propiedad = tipoPropiedad;
2665
- }
2666
- // Zonas (barrios) - Smart Zone Logic aplicado desde workflow
2667
- const zonas = this.getNodeParameter('searchZonas', i, '');
2668
- if (zonas && zonas !== '' && zonas !== 'NULL' && zonas !== 'null') {
2669
- qs.barrio = zonas; // Formato: "Núñez, Saavedra, Belgrano"
2670
- }
2671
- // Dormitorios
2672
- const dormitoriosMin = this.getNodeParameter('searchDormitoriosMin', i, 0);
2673
- if (dormitoriosMin > 0) {
2674
- qs.dormitorios_minimo = dormitoriosMin;
2675
- }
2676
- const dormitoriosMax = this.getNodeParameter('searchDormitoriosMax', i, 0);
2677
- if (dormitoriosMax > 0) {
2678
- qs.dormitorios_maximo = dormitoriosMax;
2679
- }
2680
- // Baños
2681
- const banos = this.getNodeParameter('searchBanos', i, 0);
2682
- if (banos > 0) {
2683
- qs.banos_minimo = banos;
2684
- }
2685
- // Ambientes
2686
- const ambientesMin = this.getNodeParameter('searchAmbientesMin', i, 0);
2687
- if (ambientesMin > 0) {
2688
- qs.ambientes_minimo = ambientesMin;
2689
- }
2690
- const ambientesMax = this.getNodeParameter('searchAmbientesMax', i, 0);
2691
- if (ambientesMax > 0) {
2692
- qs.ambientes_maximo = ambientesMax;
2693
- }
2694
- // Área (Superficie)
2695
- const areaMin = this.getNodeParameter('searchAreaMin', i, 0);
2696
- if (areaMin > 0) {
2697
- qs.area_minima = areaMin;
2698
- }
2699
- const areaMax = this.getNodeParameter('searchAreaMax', i, 0);
2700
- if (areaMax > 0) {
2701
- qs.area_maxima = areaMax;
2702
- }
2703
- // Moneda
2704
- const moneda = this.getNodeParameter('searchMoneda', i, 'USD');
2705
- if (moneda && moneda !== '') {
2706
- qs.moneda = moneda;
2505
+ {
2506
+ displayName: 'Operación',
2507
+ name: 'operation',
2508
+ type: 'options',
2509
+ noDataExpression: true,
2510
+ displayOptions: {
2511
+ show: {
2512
+ resource: ['ownerExpense'],
2513
+ },
2514
+ },
2515
+ options: [
2516
+ {
2517
+ name: 'Create',
2518
+ value: 'create',
2519
+ description: 'Crear gasto de propietario',
2520
+ action: 'Crear gasto de propietario',
2521
+ },
2522
+ {
2523
+ name: 'List',
2524
+ value: 'list',
2525
+ description: 'Listar gastos de propietario',
2526
+ action: 'Listar gastos de propietario',
2527
+ },
2528
+ {
2529
+ name: 'Get',
2530
+ value: 'get',
2531
+ description: 'Obtener gasto por ID',
2532
+ action: 'Obtener gasto de propietario',
2533
+ },
2534
+ {
2535
+ name: 'Approve',
2536
+ value: 'approve',
2537
+ description: 'Aprobar gasto de propietario',
2538
+ action: 'Aprobar gasto de propietario',
2539
+ },
2540
+ {
2541
+ name: 'Reject',
2542
+ value: 'reject',
2543
+ description: 'Rechazar gasto de propietario',
2544
+ action: 'Rechazar gasto de propietario',
2545
+ },
2546
+ ],
2547
+ default: 'list',
2548
+ },
2549
+ // Owner Expense ID
2550
+ {
2551
+ displayName: 'Expense ID',
2552
+ name: 'ownerExpenseId',
2553
+ type: 'number',
2554
+ required: true,
2555
+ displayOptions: {
2556
+ show: {
2557
+ resource: ['ownerExpense'],
2558
+ operation: ['get', 'approve', 'reject'],
2559
+ },
2560
+ },
2561
+ default: 0,
2562
+ description: 'ID del gasto de propietario',
2563
+ },
2564
+ // Owner Expense Create Fields
2565
+ {
2566
+ displayName: 'Owner ID',
2567
+ name: 'ownerIdExpense',
2568
+ type: 'number',
2569
+ required: true,
2570
+ displayOptions: {
2571
+ show: {
2572
+ resource: ['ownerExpense'],
2573
+ operation: ['create'],
2574
+ },
2575
+ },
2576
+ default: 0,
2577
+ description: 'ID del propietario',
2578
+ },
2579
+ {
2580
+ displayName: 'Tipo de Gasto',
2581
+ name: 'expenseType',
2582
+ type: 'options',
2583
+ required: true,
2584
+ displayOptions: {
2585
+ show: {
2586
+ resource: ['ownerExpense'],
2587
+ operation: ['create'],
2588
+ },
2589
+ },
2590
+ options: [
2591
+ { name: 'Reparación', value: 'repair' },
2592
+ { name: 'Mantenimiento', value: 'maintenance' },
2593
+ { name: 'Impuesto', value: 'tax' },
2594
+ { name: 'Servicio', value: 'service' },
2595
+ { name: 'Seguro', value: 'insurance' },
2596
+ { name: 'Otro', value: 'other' },
2597
+ ],
2598
+ default: 'repair',
2599
+ description: 'Tipo de gasto',
2600
+ },
2601
+ {
2602
+ displayName: 'Descripción',
2603
+ name: 'expenseDescription',
2604
+ type: 'string',
2605
+ required: true,
2606
+ displayOptions: {
2607
+ show: {
2608
+ resource: ['ownerExpense'],
2609
+ operation: ['create'],
2610
+ },
2611
+ },
2612
+ default: '',
2613
+ description: 'Descripción del gasto',
2614
+ },
2615
+ {
2616
+ displayName: 'Monto',
2617
+ name: 'expenseAmount',
2618
+ type: 'number',
2619
+ required: true,
2620
+ displayOptions: {
2621
+ show: {
2622
+ resource: ['ownerExpense'],
2623
+ operation: ['create'],
2624
+ },
2625
+ },
2626
+ default: 0,
2627
+ description: 'Monto del gasto',
2628
+ },
2629
+ {
2630
+ displayName: 'Fecha del Gasto',
2631
+ name: 'expenseDate',
2632
+ type: 'string',
2633
+ required: true,
2634
+ displayOptions: {
2635
+ show: {
2636
+ resource: ['ownerExpense'],
2637
+ operation: ['create'],
2638
+ },
2639
+ },
2640
+ default: '',
2641
+ placeholder: '2025-01-15',
2642
+ description: 'Fecha del gasto (YYYY-MM-DD)',
2643
+ },
2644
+ {
2645
+ displayName: 'Property ID (Opcional)',
2646
+ name: 'propertyIdExpense',
2647
+ type: 'number',
2648
+ displayOptions: {
2649
+ show: {
2650
+ resource: ['ownerExpense'],
2651
+ operation: ['create'],
2652
+ },
2653
+ },
2654
+ default: 0,
2655
+ description: 'ID de la propiedad asociada (opcional)',
2656
+ },
2657
+ {
2658
+ displayName: 'File Path (Opcional)',
2659
+ name: 'expenseFilePath',
2660
+ type: 'string',
2661
+ displayOptions: {
2662
+ show: {
2663
+ resource: ['ownerExpense'],
2664
+ operation: ['create'],
2665
+ },
2666
+ },
2667
+ default: '',
2668
+ description: 'Ruta del archivo adjunto (opcional)',
2669
+ },
2670
+ {
2671
+ displayName: 'OCR Data (Opcional)',
2672
+ name: 'expenseOcrData',
2673
+ type: 'json',
2674
+ displayOptions: {
2675
+ show: {
2676
+ resource: ['ownerExpense'],
2677
+ operation: ['create'],
2678
+ },
2679
+ },
2680
+ default: '{}',
2681
+ description: 'Datos extraídos por OCR (opcional)',
2682
+ },
2683
+ {
2684
+ displayName: 'Notas (Opcional)',
2685
+ name: 'expenseNotes',
2686
+ type: 'string',
2687
+ displayOptions: {
2688
+ show: {
2689
+ resource: ['ownerExpense'],
2690
+ operation: ['create'],
2691
+ },
2692
+ },
2693
+ default: '',
2694
+ description: 'Notas adicionales (opcional)',
2695
+ },
2696
+ // Owner Expense Approve Fields
2697
+ {
2698
+ displayName: 'Approved By (User ID)',
2699
+ name: 'expenseApprovedBy',
2700
+ type: 'number',
2701
+ required: true,
2702
+ displayOptions: {
2703
+ show: {
2704
+ resource: ['ownerExpense'],
2705
+ operation: ['approve'],
2706
+ },
2707
+ },
2708
+ default: 0,
2709
+ description: 'ID del usuario que aprueba',
2710
+ },
2711
+ // Owner Expense Reject Fields
2712
+ {
2713
+ displayName: 'Rejected By (User ID)',
2714
+ name: 'expenseRejectedBy',
2715
+ type: 'number',
2716
+ required: true,
2717
+ displayOptions: {
2718
+ show: {
2719
+ resource: ['ownerExpense'],
2720
+ operation: ['reject'],
2721
+ },
2722
+ },
2723
+ default: 0,
2724
+ description: 'ID del usuario que rechaza',
2725
+ },
2726
+ {
2727
+ displayName: 'Razón del Rechazo',
2728
+ name: 'expenseRejectionReason',
2729
+ type: 'string',
2730
+ required: true,
2731
+ displayOptions: {
2732
+ show: {
2733
+ resource: ['ownerExpense'],
2734
+ operation: ['reject'],
2735
+ },
2736
+ },
2737
+ default: '',
2738
+ description: 'Razón por la cual se rechaza el gasto',
2739
+ },
2740
+ // Owner Expense List Filters
2741
+ {
2742
+ displayName: 'Filtrar por Owner ID',
2743
+ name: 'expenseOwnerFilter',
2744
+ type: 'number',
2745
+ displayOptions: {
2746
+ show: {
2747
+ resource: ['ownerExpense'],
2748
+ operation: ['list'],
2749
+ },
2750
+ },
2751
+ default: 0,
2752
+ description: 'Filtrar por propietario (0 = todos)',
2753
+ },
2754
+ {
2755
+ displayName: 'Filtrar por Estado',
2756
+ name: 'expenseStatusFilter',
2757
+ type: 'options',
2758
+ displayOptions: {
2759
+ show: {
2760
+ resource: ['ownerExpense'],
2761
+ operation: ['list'],
2762
+ },
2763
+ },
2764
+ options: [
2765
+ { name: 'Todos', value: '' },
2766
+ { name: 'Pendiente', value: 'pending' },
2767
+ { name: 'Aprobado', value: 'approved' },
2768
+ { name: 'Rechazado', value: 'rejected' },
2769
+ { name: 'Reembolsado', value: 'reimbursed' },
2770
+ ],
2771
+ default: '',
2772
+ description: 'Filtrar por estado',
2773
+ },
2774
+ // ============================================
2775
+ // CONTRACTOR INVOICE OPERATIONS
2776
+ // ============================================
2777
+ {
2778
+ displayName: 'Operación',
2779
+ name: 'operation',
2780
+ type: 'options',
2781
+ noDataExpression: true,
2782
+ displayOptions: {
2783
+ show: {
2784
+ resource: ['contractorInvoice'],
2785
+ },
2786
+ },
2787
+ options: [
2788
+ {
2789
+ name: 'Create',
2790
+ value: 'create',
2791
+ description: 'Crear factura de contratista',
2792
+ action: 'Crear factura de contratista',
2793
+ },
2794
+ {
2795
+ name: 'List',
2796
+ value: 'list',
2797
+ description: 'Listar facturas de contratista',
2798
+ action: 'Listar facturas de contratista',
2799
+ },
2800
+ {
2801
+ name: 'Get',
2802
+ value: 'get',
2803
+ description: 'Obtener factura por ID',
2804
+ action: 'Obtener factura de contratista',
2805
+ },
2806
+ {
2807
+ name: 'Approve',
2808
+ value: 'approve',
2809
+ description: 'Aprobar factura de contratista',
2810
+ action: 'Aprobar factura de contratista',
2811
+ },
2812
+ {
2813
+ name: 'Reject',
2814
+ value: 'reject',
2815
+ description: 'Rechazar factura de contratista',
2816
+ action: 'Rechazar factura de contratista',
2817
+ },
2818
+ {
2819
+ name: 'Mark Paid',
2820
+ value: 'markPaid',
2821
+ description: 'Marcar factura como pagada',
2822
+ action: 'Marcar factura como pagada',
2823
+ },
2824
+ ],
2825
+ default: 'list',
2826
+ },
2827
+ // Contractor Invoice ID
2828
+ {
2829
+ displayName: 'Invoice ID',
2830
+ name: 'contractorInvoiceId',
2831
+ type: 'number',
2832
+ required: true,
2833
+ displayOptions: {
2834
+ show: {
2835
+ resource: ['contractorInvoice'],
2836
+ operation: ['get', 'approve', 'reject', 'markPaid'],
2837
+ },
2838
+ },
2839
+ default: 0,
2840
+ description: 'ID de la factura de contratista',
2841
+ },
2842
+ // Contractor Invoice Create Fields
2843
+ {
2844
+ displayName: 'Contractor ID',
2845
+ name: 'contractorIdInvoice',
2846
+ type: 'number',
2847
+ required: true,
2848
+ displayOptions: {
2849
+ show: {
2850
+ resource: ['contractorInvoice'],
2851
+ operation: ['create'],
2852
+ },
2853
+ },
2854
+ default: 0,
2855
+ description: 'ID del contratista',
2856
+ },
2857
+ {
2858
+ displayName: 'Tipo de Factura',
2859
+ name: 'invoiceType',
2860
+ type: 'options',
2861
+ required: true,
2862
+ displayOptions: {
2863
+ show: {
2864
+ resource: ['contractorInvoice'],
2865
+ operation: ['create'],
2866
+ },
2867
+ },
2868
+ options: [
2869
+ { name: 'Servicio', value: 'service' },
2870
+ { name: 'Materiales', value: 'materials' },
2871
+ { name: 'Presupuesto', value: 'quote' },
2872
+ ],
2873
+ default: 'service',
2874
+ description: 'Tipo de factura',
2875
+ },
2876
+ {
2877
+ displayName: 'Número de Factura',
2878
+ name: 'invoiceNumber',
2879
+ type: 'string',
2880
+ required: true,
2881
+ displayOptions: {
2882
+ show: {
2883
+ resource: ['contractorInvoice'],
2884
+ operation: ['create'],
2885
+ },
2886
+ },
2887
+ default: '',
2888
+ description: 'Número único de la factura',
2889
+ },
2890
+ {
2891
+ displayName: 'Monto',
2892
+ name: 'invoiceAmount',
2893
+ type: 'number',
2894
+ required: true,
2895
+ displayOptions: {
2896
+ show: {
2897
+ resource: ['contractorInvoice'],
2898
+ operation: ['create'],
2899
+ },
2900
+ },
2901
+ default: 0,
2902
+ description: 'Monto de la factura',
2903
+ },
2904
+ {
2905
+ displayName: 'Fecha de Emisión',
2906
+ name: 'invoiceIssueDate',
2907
+ type: 'string',
2908
+ required: true,
2909
+ displayOptions: {
2910
+ show: {
2911
+ resource: ['contractorInvoice'],
2912
+ operation: ['create'],
2913
+ },
2914
+ },
2915
+ default: '',
2916
+ placeholder: '2025-01-15',
2917
+ description: 'Fecha de emisión de la factura (YYYY-MM-DD)',
2918
+ },
2919
+ {
2920
+ displayName: 'Fecha de Vencimiento (Opcional)',
2921
+ name: 'invoiceDueDate',
2922
+ type: 'string',
2923
+ displayOptions: {
2924
+ show: {
2925
+ resource: ['contractorInvoice'],
2926
+ operation: ['create'],
2927
+ },
2928
+ },
2929
+ default: '',
2930
+ placeholder: '2025-02-15',
2931
+ description: 'Fecha de vencimiento (YYYY-MM-DD)',
2932
+ },
2933
+ {
2934
+ displayName: 'Maintenance Request ID (Opcional)',
2935
+ name: 'maintenanceRequestIdInvoice',
2936
+ type: 'number',
2937
+ displayOptions: {
2938
+ show: {
2939
+ resource: ['contractorInvoice'],
2940
+ operation: ['create'],
2941
+ },
2942
+ },
2943
+ default: 0,
2944
+ description: 'ID de la solicitud de mantenimiento asociada (opcional)',
2945
+ },
2946
+ {
2947
+ displayName: 'File Path (Opcional)',
2948
+ name: 'invoiceFilePath',
2949
+ type: 'string',
2950
+ displayOptions: {
2951
+ show: {
2952
+ resource: ['contractorInvoice'],
2953
+ operation: ['create'],
2954
+ },
2955
+ },
2956
+ default: '',
2957
+ description: 'Ruta del archivo de la factura (opcional)',
2958
+ },
2959
+ {
2960
+ displayName: 'OCR Data (Opcional)',
2961
+ name: 'invoiceOcrData',
2962
+ type: 'json',
2963
+ displayOptions: {
2964
+ show: {
2965
+ resource: ['contractorInvoice'],
2966
+ operation: ['create'],
2967
+ },
2968
+ },
2969
+ default: '{}',
2970
+ description: 'Datos extraídos por OCR (opcional)',
2971
+ },
2972
+ {
2973
+ displayName: 'Notas (Opcional)',
2974
+ name: 'invoiceNotes',
2975
+ type: 'string',
2976
+ displayOptions: {
2977
+ show: {
2978
+ resource: ['contractorInvoice'],
2979
+ operation: ['create'],
2980
+ },
2981
+ },
2982
+ default: '',
2983
+ description: 'Notas adicionales (opcional)',
2984
+ },
2985
+ // Contractor Invoice Reject Fields
2986
+ {
2987
+ displayName: 'Razón del Rechazo',
2988
+ name: 'invoiceRejectionReason',
2989
+ type: 'string',
2990
+ required: true,
2991
+ displayOptions: {
2992
+ show: {
2993
+ resource: ['contractorInvoice'],
2994
+ operation: ['reject'],
2995
+ },
2996
+ },
2997
+ default: '',
2998
+ description: 'Razón por la cual se rechaza la factura',
2999
+ },
3000
+ // Contractor Invoice Mark Paid Fields
3001
+ {
3002
+ displayName: 'Referencia de Pago (Opcional)',
3003
+ name: 'invoicePaymentReference',
3004
+ type: 'string',
3005
+ displayOptions: {
3006
+ show: {
3007
+ resource: ['contractorInvoice'],
3008
+ operation: ['markPaid'],
3009
+ },
3010
+ },
3011
+ default: '',
3012
+ description: 'Número de referencia del pago (opcional)',
3013
+ },
3014
+ {
3015
+ displayName: 'Fecha de Pago (Opcional)',
3016
+ name: 'invoicePaidAt',
3017
+ type: 'string',
3018
+ displayOptions: {
3019
+ show: {
3020
+ resource: ['contractorInvoice'],
3021
+ operation: ['markPaid'],
3022
+ },
3023
+ },
3024
+ default: '',
3025
+ placeholder: '2025-01-20',
3026
+ description: 'Fecha del pago (YYYY-MM-DD), por defecto hoy',
3027
+ },
3028
+ // Contractor Invoice List Filters
3029
+ {
3030
+ displayName: 'Filtrar por Contractor ID',
3031
+ name: 'invoiceContractorFilter',
3032
+ type: 'number',
3033
+ displayOptions: {
3034
+ show: {
3035
+ resource: ['contractorInvoice'],
3036
+ operation: ['list'],
3037
+ },
3038
+ },
3039
+ default: 0,
3040
+ description: 'Filtrar por contratista (0 = todos)',
3041
+ },
3042
+ {
3043
+ displayName: 'Filtrar por Estado',
3044
+ name: 'invoiceStatusFilter',
3045
+ type: 'options',
3046
+ displayOptions: {
3047
+ show: {
3048
+ resource: ['contractorInvoice'],
3049
+ operation: ['list'],
3050
+ },
3051
+ },
3052
+ options: [
3053
+ { name: 'Todos', value: '' },
3054
+ { name: 'Pendiente', value: 'pending' },
3055
+ { name: 'Aprobada', value: 'approved' },
3056
+ { name: 'Rechazada', value: 'rejected' },
3057
+ { name: 'Pagada', value: 'paid' },
3058
+ ],
3059
+ default: '',
3060
+ description: 'Filtrar por estado',
3061
+ },
3062
+ {
3063
+ displayName: 'Filtrar por Tipo',
3064
+ name: 'invoiceTypeFilter',
3065
+ type: 'options',
3066
+ displayOptions: {
3067
+ show: {
3068
+ resource: ['contractorInvoice'],
3069
+ operation: ['list'],
3070
+ },
3071
+ },
3072
+ options: [
3073
+ { name: 'Todos', value: '' },
3074
+ { name: 'Servicio', value: 'service' },
3075
+ { name: 'Materiales', value: 'materials' },
3076
+ { name: 'Presupuesto', value: 'quote' },
3077
+ ],
3078
+ default: '',
3079
+ description: 'Filtrar por tipo de factura',
3080
+ },
3081
+ // ============================================
3082
+ // PAYMENT PROOF OPERATIONS
3083
+ // ============================================
3084
+ {
3085
+ displayName: 'Operación',
3086
+ name: 'operation',
3087
+ type: 'options',
3088
+ noDataExpression: true,
3089
+ displayOptions: {
3090
+ show: {
3091
+ resource: ['paymentProof'],
3092
+ },
3093
+ },
3094
+ options: [
3095
+ {
3096
+ name: 'Process',
3097
+ value: 'process',
3098
+ description: 'Procesar comprobante de pago',
3099
+ action: 'Procesar comprobante',
3100
+ },
3101
+ ],
3102
+ default: 'process',
3103
+ },
3104
+ {
3105
+ displayName: 'ID del Inquilino',
3106
+ name: 'ppTenantId',
3107
+ type: 'string',
3108
+ required: true,
3109
+ displayOptions: {
3110
+ show: {
3111
+ resource: ['paymentProof'],
3112
+ operation: ['process'],
3113
+ },
3114
+ },
3115
+ default: '',
3116
+ placeholder: '123',
3117
+ description: 'ID del inquilino que realiza el pago',
3118
+ },
3119
+ {
3120
+ displayName: 'Datos OCR del Comprobante',
3121
+ name: 'ppOcrData',
3122
+ type: 'json',
3123
+ required: true,
3124
+ displayOptions: {
3125
+ show: {
3126
+ resource: ['paymentProof'],
3127
+ operation: ['process'],
3128
+ },
3129
+ },
3130
+ default: '{"amount": 45000, "date": "2026-01-26", "reference": "REF123"}',
3131
+ description: 'Datos extraídos del comprobante por OCR (amount, date, reference)',
3132
+ },
3133
+ {
3134
+ displayName: 'URL del Archivo (Opcional)',
3135
+ name: 'ppFileUrl',
3136
+ type: 'string',
3137
+ displayOptions: {
3138
+ show: {
3139
+ resource: ['paymentProof'],
3140
+ operation: ['process'],
3141
+ },
3142
+ },
3143
+ default: '',
3144
+ placeholder: 'https://storage.ejemplo.com/comprobante.pdf',
3145
+ description: 'URL del comprobante escaneado',
3146
+ },
3147
+ // ============================================
3148
+ // AUTOMATION OPERATIONS
3149
+ // ============================================
3150
+ {
3151
+ displayName: 'Operación',
3152
+ name: 'operation',
3153
+ type: 'options',
3154
+ noDataExpression: true,
3155
+ displayOptions: {
3156
+ show: {
3157
+ resource: ['automation'],
3158
+ },
3159
+ },
3160
+ options: [
3161
+ {
3162
+ name: 'Log Execution',
3163
+ value: 'logExecution',
3164
+ description: 'Registrar ejecución de workflow',
3165
+ action: 'Registrar ejecución',
3166
+ },
3167
+ ],
3168
+ default: 'logExecution',
3169
+ },
3170
+ {
3171
+ displayName: 'Nombre del Workflow',
3172
+ name: 'autoWorkflowName',
3173
+ type: 'string',
3174
+ required: true,
3175
+ displayOptions: {
3176
+ show: {
3177
+ resource: ['automation'],
3178
+ operation: ['logExecution'],
3179
+ },
3180
+ },
3181
+ default: '',
3182
+ placeholder: 'ProcessPaymentProof',
3183
+ description: 'Nombre identificador del workflow ejecutado',
3184
+ },
3185
+ {
3186
+ displayName: 'ID de Ejecución',
3187
+ name: 'autoExecutionId',
3188
+ type: 'string',
3189
+ required: true,
3190
+ displayOptions: {
3191
+ show: {
3192
+ resource: ['automation'],
3193
+ operation: ['logExecution'],
3194
+ },
3195
+ },
3196
+ default: '',
3197
+ placeholder: 'exec_123456',
3198
+ description: 'ID único de la ejecución (execution ID de N8N)',
3199
+ },
3200
+ {
3201
+ displayName: 'Estado',
3202
+ name: 'autoStatus',
3203
+ type: 'options',
3204
+ required: true,
3205
+ displayOptions: {
3206
+ show: {
3207
+ resource: ['automation'],
3208
+ operation: ['logExecution'],
3209
+ },
3210
+ },
3211
+ options: [
3212
+ { name: 'Success', value: 'success' },
3213
+ { name: 'Failed', value: 'failed' },
3214
+ { name: 'Pending', value: 'pending' },
3215
+ ],
3216
+ default: 'success',
3217
+ description: 'Estado de la ejecución',
3218
+ },
3219
+ {
3220
+ displayName: 'Tipo de Documento',
3221
+ name: 'autoDocumentType',
3222
+ type: 'options',
3223
+ displayOptions: {
3224
+ show: {
3225
+ resource: ['automation'],
3226
+ operation: ['logExecution'],
3227
+ },
3228
+ },
3229
+ options: [
3230
+ { name: 'Payment Proof', value: 'payment_proof' },
3231
+ { name: 'Contractor Invoice', value: 'contractor_invoice' },
3232
+ { name: 'Owner Expense', value: 'owner_expense' },
3233
+ { name: 'Other', value: 'other' },
3234
+ ],
3235
+ default: 'payment_proof',
3236
+ description: 'Tipo de documento procesado (opcional)',
3237
+ },
3238
+ {
3239
+ displayName: 'Metadata (JSON)',
3240
+ name: 'autoMetadata',
3241
+ type: 'json',
3242
+ displayOptions: {
3243
+ show: {
3244
+ resource: ['automation'],
3245
+ operation: ['logExecution'],
3246
+ },
3247
+ },
3248
+ default: '{}',
3249
+ description: 'Datos adicionales de la ejecución (opcional)',
3250
+ },
3251
+ // ============================================
3252
+ // POLICY OPERATIONS
3253
+ // ============================================
3254
+ {
3255
+ displayName: 'Operación',
3256
+ name: 'operation',
3257
+ type: 'options',
3258
+ noDataExpression: true,
3259
+ displayOptions: {
3260
+ show: {
3261
+ resource: ['policy'],
3262
+ },
3263
+ },
3264
+ options: [
3265
+ {
3266
+ name: 'Evaluate',
3267
+ value: 'evaluate',
3268
+ description: 'Evaluar política de aprobación',
3269
+ action: 'Evaluar política',
3270
+ },
3271
+ ],
3272
+ default: 'evaluate',
3273
+ },
3274
+ {
3275
+ displayName: 'Tipo de Política',
3276
+ name: 'policyType',
3277
+ type: 'options',
3278
+ required: true,
3279
+ displayOptions: {
3280
+ show: {
3281
+ resource: ['policy'],
3282
+ operation: ['evaluate'],
3283
+ },
3284
+ },
3285
+ options: [
3286
+ { name: 'Payment Proof', value: 'payment_proof' },
3287
+ { name: 'Contractor Invoice', value: 'contractor_invoice' },
3288
+ { name: 'Owner Expense', value: 'owner_expense' },
3289
+ ],
3290
+ default: 'payment_proof',
3291
+ description: 'Tipo de documento a evaluar',
3292
+ },
3293
+ {
3294
+ displayName: 'Monto',
3295
+ name: 'policyAmount',
3296
+ type: 'number',
3297
+ required: true,
3298
+ displayOptions: {
3299
+ show: {
3300
+ resource: ['policy'],
3301
+ operation: ['evaluate'],
3302
+ },
3303
+ },
3304
+ default: 0,
3305
+ placeholder: '45000',
3306
+ description: 'Monto total del documento. Ej: 45000.50',
3307
+ },
3308
+ {
3309
+ displayName: 'Datos del Documento (JSON)',
3310
+ name: 'policyDocumentData',
3311
+ type: 'json',
3312
+ required: true,
3313
+ displayOptions: {
3314
+ show: {
3315
+ resource: ['policy'],
3316
+ operation: ['evaluate'],
3317
+ },
3318
+ },
3319
+ default: '{"amount": 45000, "date": "2026-01-26", "tenant_id": 123}',
3320
+ description: 'Datos extraídos del documento para evaluación',
3321
+ },
3322
+ // ============================================
3323
+ // NOTIFICATION OPERATIONS
3324
+ // ============================================
3325
+ {
3326
+ displayName: 'Operación',
3327
+ name: 'operation',
3328
+ type: 'options',
3329
+ noDataExpression: true,
3330
+ displayOptions: {
3331
+ show: {
3332
+ resource: ['notification'],
3333
+ },
3334
+ },
3335
+ options: [
3336
+ {
3337
+ name: 'Send',
3338
+ value: 'send',
3339
+ description: 'Enviar notificación',
3340
+ action: 'Enviar notificación',
3341
+ },
3342
+ ],
3343
+ default: 'send',
3344
+ },
3345
+ {
3346
+ displayName: 'Tipo de Destinatario',
3347
+ name: 'notifRecipientType',
3348
+ type: 'options',
3349
+ required: true,
3350
+ displayOptions: {
3351
+ show: {
3352
+ resource: ['notification'],
3353
+ operation: ['send'],
3354
+ },
3355
+ },
3356
+ options: [
3357
+ { name: 'Tenant', value: 'tenant' },
3358
+ { name: 'Owner', value: 'owner' },
3359
+ { name: 'Contractor', value: 'contractor' },
3360
+ { name: 'Admin', value: 'admin' },
3361
+ ],
3362
+ default: 'tenant',
3363
+ description: 'Tipo de destinatario de la notificación',
3364
+ },
3365
+ {
3366
+ displayName: 'ID del Destinatario',
3367
+ name: 'notifRecipientId',
3368
+ type: 'string',
3369
+ required: true,
3370
+ displayOptions: {
3371
+ show: {
3372
+ resource: ['notification'],
3373
+ operation: ['send'],
3374
+ },
3375
+ },
3376
+ default: '',
3377
+ placeholder: '123',
3378
+ description: 'ID del usuario destinatario',
3379
+ },
3380
+ {
3381
+ displayName: 'Canal',
3382
+ name: 'notifChannel',
3383
+ type: 'options',
3384
+ required: true,
3385
+ displayOptions: {
3386
+ show: {
3387
+ resource: ['notification'],
3388
+ operation: ['send'],
3389
+ },
3390
+ },
3391
+ options: [
3392
+ { name: 'Database (In-App)', value: 'database' },
3393
+ { name: 'WhatsApp', value: 'whatsapp' },
3394
+ { name: 'Email', value: 'email' },
3395
+ ],
3396
+ default: 'database',
3397
+ description: 'Canal de envío de la notificación',
3398
+ },
3399
+ {
3400
+ displayName: 'Clave de Template',
3401
+ name: 'notifTemplateKey',
3402
+ type: 'string',
3403
+ required: true,
3404
+ displayOptions: {
3405
+ show: {
3406
+ resource: ['notification'],
3407
+ operation: ['send'],
3408
+ },
3409
+ },
3410
+ default: '',
3411
+ placeholder: 'payment_received',
3412
+ description: 'Clave del template de notificación. Ej: payment_received, invoice_approved',
3413
+ },
3414
+ {
3415
+ displayName: 'Variables del Template (JSON)',
3416
+ name: 'notifVariables',
3417
+ type: 'json',
3418
+ displayOptions: {
3419
+ show: {
3420
+ resource: ['notification'],
3421
+ operation: ['send'],
3422
+ },
3423
+ },
3424
+ default: '{"amount": 45000, "property_address": "Av. Corrientes 1234"}',
3425
+ description: 'Variables para reemplazar en el template (opcional)',
3426
+ },
3427
+ ],
3428
+ };
3429
+ }
3430
+ async execute() {
3431
+ const items = this.getInputData();
3432
+ const returnData = [];
3433
+ const credentials = await this.getCredentials('aivenceRealtyApi');
3434
+ const baseUrl = credentials.baseUrl;
3435
+ for (let i = 0; i < items.length; i++) {
3436
+ try {
3437
+ const resource = this.getNodeParameter('resource', i);
3438
+ const operation = this.getNodeParameter('operation', i);
3439
+ let responseData;
3440
+ // ============================================
3441
+ // LEAD RESOURCE
3442
+ // ============================================
3443
+ if (resource === 'lead') {
3444
+ if (operation === 'list') {
3445
+ responseData = await this.helpers.httpRequest({
3446
+ method: 'GET',
3447
+ url: `${baseUrl}/api/v1/leads`,
3448
+ json: true,
3449
+ });
3450
+ }
3451
+ else if (operation === 'create') {
3452
+ const useJsonInput = this.getNodeParameter('useJsonInput', i, false);
3453
+ let body = {};
3454
+ if (useJsonInput) {
3455
+ // Usar JSON del nodo anterior
3456
+ body = items[i].json;
3457
+ }
3458
+ else {
3459
+ // Construir body con campos estructurados
3460
+ body = {
3461
+ name: this.getNodeParameter('name', i),
3462
+ email: this.getNodeParameter('email', i),
3463
+ phone: this.getNodeParameter('phone', i),
3464
+ message: this.getNodeParameter('message', i, ''),
3465
+ interest: this.getNodeParameter('interest', i, 'compra'),
3466
+ budget_min: this.getNodeParameter('budget_min', i, 0),
3467
+ budget_max: this.getNodeParameter('budget_max', i, 0),
3468
+ property_type: this.getNodeParameter('property_type', i, 'apartamento'),
3469
+ conversation_id: this.getNodeParameter('conversation_id', i, ''),
3470
+ status: this.getNodeParameter('status', i, 'new'),
3471
+ score: this.getNodeParameter('score', i, 0),
3472
+ category: this.getNodeParameter('category', i, 'warm'),
3473
+ notes: this.getNodeParameter('notes', i, ''),
3474
+ crm_id: this.getNodeParameter('crm_id', i, ''),
3475
+ };
3476
+ // Procesar preferred_areas (convertir string separado por comas a array)
3477
+ const areasString = this.getNodeParameter('preferred_areas', i, '');
3478
+ if (areasString) {
3479
+ body.preferred_areas = areasString.split(',').map((area) => area.trim());
3480
+ }
3481
+ // Procesar tags (convertir string separado por comas a array)
3482
+ const tagsString = this.getNodeParameter('tags', i, '');
3483
+ if (tagsString) {
3484
+ body.tags = tagsString.split(',').map((tag) => tag.trim());
3485
+ }
3486
+ // Remover campos vacíos opcionales
3487
+ if (!body.message)
3488
+ delete body.message;
3489
+ if (body.budget_min === 0)
3490
+ delete body.budget_min;
3491
+ if (body.budget_max === 0)
3492
+ delete body.budget_max;
3493
+ if (!body.preferred_areas || body.preferred_areas.length === 0)
3494
+ delete body.preferred_areas;
3495
+ if (!body.conversation_id)
3496
+ delete body.conversation_id;
3497
+ if (!body.notes)
3498
+ delete body.notes;
3499
+ if (!body.crm_id)
3500
+ delete body.crm_id;
3501
+ if (!body.tags || body.tags.length === 0)
3502
+ delete body.tags;
3503
+ if (body.score === 0)
3504
+ delete body.score;
3505
+ }
3506
+ responseData = await this.helpers.httpRequest({
3507
+ method: 'POST',
3508
+ url: `${baseUrl}/api/v1/leads`,
3509
+ body,
3510
+ json: true,
3511
+ });
3512
+ }
3513
+ else if (operation === 'update') {
3514
+ const leadId = this.getNodeParameter('leadId', i);
3515
+ const useJsonInput = this.getNodeParameter('useJsonInput', i, false);
3516
+ let body = {};
3517
+ if (useJsonInput) {
3518
+ // Usar JSON del nodo anterior
3519
+ body = items[i].json;
3520
+ }
3521
+ else {
3522
+ // Construir body con campos estructurados (solo enviar campos proporcionados)
3523
+ const name = this.getNodeParameter('name', i, '');
3524
+ const email = this.getNodeParameter('email', i, '');
3525
+ const phone = this.getNodeParameter('phone', i, '');
3526
+ const message = this.getNodeParameter('message', i, '');
3527
+ const interest = this.getNodeParameter('interest', i, '');
3528
+ const budget_min = this.getNodeParameter('budget_min', i, 0);
3529
+ const budget_max = this.getNodeParameter('budget_max', i, 0);
3530
+ const property_type = this.getNodeParameter('property_type', i, '');
3531
+ const areasString = this.getNodeParameter('preferred_areas', i, '');
3532
+ const conversation_id = this.getNodeParameter('conversation_id', i, '');
3533
+ const status = this.getNodeParameter('status', i, '');
3534
+ const score = this.getNodeParameter('score', i, 0);
3535
+ const category = this.getNodeParameter('category', i, '');
3536
+ const notes = this.getNodeParameter('notes', i, '');
3537
+ const crm_id = this.getNodeParameter('crm_id', i, '');
3538
+ const tagsString = this.getNodeParameter('tags', i, '');
3539
+ if (name)
3540
+ body.name = name;
3541
+ if (email)
3542
+ body.email = email;
3543
+ if (phone)
3544
+ body.phone = phone;
3545
+ if (message)
3546
+ body.message = message;
3547
+ if (interest)
3548
+ body.interest = interest;
3549
+ if (budget_min > 0)
3550
+ body.budget_min = budget_min;
3551
+ if (budget_max > 0)
3552
+ body.budget_max = budget_max;
3553
+ if (property_type)
3554
+ body.property_type = property_type;
3555
+ if (conversation_id)
3556
+ body.conversation_id = conversation_id;
3557
+ if (status)
3558
+ body.status = status;
3559
+ if (score > 0)
3560
+ body.score = score;
3561
+ if (category)
3562
+ body.category = category;
3563
+ if (notes)
3564
+ body.notes = notes;
3565
+ if (crm_id)
3566
+ body.crm_id = crm_id;
3567
+ if (areasString) {
3568
+ body.preferred_areas = areasString.split(',').map((area) => area.trim());
3569
+ }
3570
+ if (tagsString) {
3571
+ body.tags = tagsString.split(',').map((tag) => tag.trim());
3572
+ }
3573
+ }
3574
+ responseData = await this.helpers.httpRequest({
3575
+ method: 'PATCH',
3576
+ url: `${baseUrl}/api/v1/leads/${leadId}`,
3577
+ body,
3578
+ json: true,
3579
+ });
3580
+ }
3581
+ else if (operation === 'get') {
3582
+ const leadId = this.getNodeParameter('leadId', i);
3583
+ responseData = await this.helpers.httpRequest({
3584
+ method: 'GET',
3585
+ url: `${baseUrl}/api/v1/leads/${leadId}`,
3586
+ json: true,
3587
+ });
3588
+ }
3589
+ else if (operation === 'addActivity') {
3590
+ const leadId = this.getNodeParameter('leadId', i);
3591
+ const activityType = this.getNodeParameter('activity_type', i);
3592
+ const description = this.getNodeParameter('activity_description', i);
3593
+ const body = {
3594
+ activity_type: activityType,
3595
+ description: description,
3596
+ };
3597
+ responseData = await this.helpers.httpRequest({
3598
+ method: 'POST',
3599
+ url: `${baseUrl}/api/v1/mariana/leads/${leadId}/activity`,
3600
+ body,
3601
+ json: true,
3602
+ });
3603
+ }
3604
+ }
3605
+ // ============================================
3606
+ // PROPERTY RESOURCE
3607
+ // ============================================
3608
+ else if (resource === 'property') {
3609
+ if (operation === 'search') {
3610
+ // ============================================
3611
+ // PROPERTY SEARCH - Smart Zone Logic compatible
3612
+ // ============================================
3613
+ const qs = {};
3614
+ // Property ID (búsqueda directa por ID)
3615
+ const propertyId = this.getNodeParameter('searchPropertyId', i, 0);
3616
+ if (propertyId > 0) {
3617
+ qs.id = propertyId;
3618
+ }
3619
+ // Operación -> Estado (Laravel espera "estado": "for_rent" o "for_sale")
3620
+ const operacion = this.getNodeParameter('searchOperacion', i, '');
3621
+ if (operacion && operacion !== 'NULL' && operacion !== '') {
3622
+ qs.estado = operacion;
3623
+ }
3624
+ // Tipo de Propiedad
3625
+ const tipoPropiedad = this.getNodeParameter('searchTipoPropiedad', i, '');
3626
+ if (tipoPropiedad && tipoPropiedad !== '' && tipoPropiedad !== 'NULL') {
3627
+ qs.tipo_propiedad = tipoPropiedad;
3628
+ }
3629
+ // Zonas (barrios) - Smart Zone Logic aplicado desde workflow
3630
+ const zonas = this.getNodeParameter('searchZonas', i, '');
3631
+ if (zonas && zonas !== '' && zonas !== 'NULL' && zonas !== 'null') {
3632
+ qs.barrio = zonas; // Formato: "Núñez, Saavedra, Belgrano"
3633
+ }
3634
+ // Dormitorios
3635
+ const dormitoriosMin = this.getNodeParameter('searchDormitoriosMin', i, 0);
3636
+ if (dormitoriosMin > 0) {
3637
+ qs.dormitorios_minimo = dormitoriosMin;
3638
+ }
3639
+ const dormitoriosMax = this.getNodeParameter('searchDormitoriosMax', i, 0);
3640
+ if (dormitoriosMax > 0) {
3641
+ qs.dormitorios_maximo = dormitoriosMax;
3642
+ }
3643
+ // Baños
3644
+ const banos = this.getNodeParameter('searchBanos', i, 0);
3645
+ if (banos > 0) {
3646
+ qs.banos_minimo = banos;
3647
+ }
3648
+ // Ambientes
3649
+ const ambientesMin = this.getNodeParameter('searchAmbientesMin', i, 0);
3650
+ if (ambientesMin > 0) {
3651
+ qs.ambientes_minimo = ambientesMin;
3652
+ }
3653
+ const ambientesMax = this.getNodeParameter('searchAmbientesMax', i, 0);
3654
+ if (ambientesMax > 0) {
3655
+ qs.ambientes_maximo = ambientesMax;
3656
+ }
3657
+ // Área (Superficie)
3658
+ const areaMin = this.getNodeParameter('searchAreaMin', i, 0);
3659
+ if (areaMin > 0) {
3660
+ qs.area_minima = areaMin;
3661
+ }
3662
+ const areaMax = this.getNodeParameter('searchAreaMax', i, 0);
3663
+ if (areaMax > 0) {
3664
+ qs.area_maxima = areaMax;
3665
+ }
3666
+ // Moneda
3667
+ const moneda = this.getNodeParameter('searchMoneda', i, 'USD');
3668
+ if (moneda && moneda !== '') {
3669
+ qs.moneda = moneda;
2707
3670
  }
2708
3671
  // Precio
2709
3672
  const precioMin = this.getNodeParameter('searchPrecioMin', i, 0);
@@ -3138,11 +4101,19 @@ class AivenceRealty {
3138
4101
  json: true,
3139
4102
  });
3140
4103
  }
4104
+ else if (operation === 'validateExists') {
4105
+ const tenantId = this.getNodeParameter('tenantId', i);
4106
+ responseData = await this.helpers.httpRequest({
4107
+ method: 'GET',
4108
+ url: `${baseUrl}/api/v1/tenants/${tenantId}/exists`,
4109
+ json: true,
4110
+ });
4111
+ }
3141
4112
  }
3142
4113
  // ============================================
3143
4114
  // APPOINTMENT RESOURCE
3144
4115
  // ============================================
3145
- else if (resource === 'appointment') {
4116
+ else if (resource == 'appointment') {
3146
4117
  if (operation === 'list') {
3147
4118
  const propertyIdFilter = this.getNodeParameter('appointmentPropertyIdFilter', i, 0);
3148
4119
  const staffIdFilter = this.getNodeParameter('appointmentStaffIdFilter', i, 0);
@@ -3399,6 +4370,340 @@ class AivenceRealty {
3399
4370
  });
3400
4371
  }
3401
4372
  }
4373
+ // ============================================
4374
+ // OWNER EXPENSE RESOURCE
4375
+ // ============================================
4376
+ else if (resource === 'ownerExpense') {
4377
+ if (operation === 'create') {
4378
+ const ownerId = this.getNodeParameter('ownerIdExpense', i);
4379
+ const expenseType = this.getNodeParameter('expenseType', i);
4380
+ const description = this.getNodeParameter('expenseDescription', i);
4381
+ const amount = this.getNodeParameter('expenseAmount', i);
4382
+ const expenseDate = this.getNodeParameter('expenseDate', i);
4383
+ const propertyId = this.getNodeParameter('propertyIdExpense', i);
4384
+ const filePath = this.getNodeParameter('expenseFilePath', i);
4385
+ const ocrData = this.getNodeParameter('expenseOcrData', i);
4386
+ const notes = this.getNodeParameter('expenseNotes', i);
4387
+ const body = {
4388
+ owner_id: ownerId,
4389
+ expense_type: expenseType,
4390
+ description,
4391
+ amount,
4392
+ expense_date: expenseDate,
4393
+ };
4394
+ if (propertyId)
4395
+ body.property_id = propertyId;
4396
+ if (filePath)
4397
+ body.file_path = filePath;
4398
+ if (ocrData && ocrData !== '{}')
4399
+ body.ocr_data = JSON.parse(ocrData);
4400
+ if (notes)
4401
+ body.notes = notes;
4402
+ responseData = await this.helpers.httpRequest({
4403
+ method: 'POST',
4404
+ url: `${baseUrl}/api/v1/owner-expenses`,
4405
+ headers: {
4406
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4407
+ 'Accept': 'application/json',
4408
+ 'Content-Type': 'application/json',
4409
+ },
4410
+ body,
4411
+ json: true,
4412
+ });
4413
+ }
4414
+ else if (operation === 'list') {
4415
+ const ownerFilter = this.getNodeParameter('expenseOwnerFilter', i);
4416
+ const statusFilter = this.getNodeParameter('expenseStatusFilter', i);
4417
+ let url = `${baseUrl}/api/v1/owner-expenses`;
4418
+ const params = [];
4419
+ if (ownerFilter)
4420
+ params.push(`owner_id=${ownerFilter}`);
4421
+ if (statusFilter)
4422
+ params.push(`status=${statusFilter}`);
4423
+ if (params.length > 0)
4424
+ url += `?${params.join('&')}`;
4425
+ responseData = await this.helpers.httpRequest({
4426
+ method: 'GET',
4427
+ url,
4428
+ headers: {
4429
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4430
+ 'Accept': 'application/json',
4431
+ },
4432
+ json: true,
4433
+ });
4434
+ }
4435
+ else if (operation === 'get') {
4436
+ const expenseId = this.getNodeParameter('ownerExpenseId', i);
4437
+ responseData = await this.helpers.httpRequest({
4438
+ method: 'GET',
4439
+ url: `${baseUrl}/api/v1/owner-expenses/${expenseId}`,
4440
+ headers: {
4441
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4442
+ 'Accept': 'application/json',
4443
+ },
4444
+ json: true,
4445
+ });
4446
+ }
4447
+ else if (operation === 'approve') {
4448
+ const expenseId = this.getNodeParameter('ownerExpenseId', i);
4449
+ const approvedBy = this.getNodeParameter('expenseApprovedBy', i);
4450
+ responseData = await this.helpers.httpRequest({
4451
+ method: 'POST',
4452
+ url: `${baseUrl}/api/v1/owner-expenses/${expenseId}/approve`,
4453
+ headers: {
4454
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4455
+ 'Accept': 'application/json',
4456
+ 'Content-Type': 'application/json',
4457
+ },
4458
+ body: { approved_by: approvedBy },
4459
+ json: true,
4460
+ });
4461
+ }
4462
+ else if (operation === 'reject') {
4463
+ const expenseId = this.getNodeParameter('ownerExpenseId', i);
4464
+ const rejectedBy = this.getNodeParameter('expenseRejectedBy', i);
4465
+ const rejectionReason = this.getNodeParameter('expenseRejectionReason', i);
4466
+ responseData = await this.helpers.httpRequest({
4467
+ method: 'POST',
4468
+ url: `${baseUrl}/api/v1/owner-expenses/${expenseId}/reject`,
4469
+ headers: {
4470
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4471
+ 'Accept': 'application/json',
4472
+ 'Content-Type': 'application/json',
4473
+ },
4474
+ body: {
4475
+ rejected_by: rejectedBy,
4476
+ rejection_reason: rejectionReason,
4477
+ },
4478
+ json: true,
4479
+ });
4480
+ }
4481
+ }
4482
+ // ============================================
4483
+ // CONTRACTOR INVOICE RESOURCE
4484
+ // ============================================
4485
+ else if (resource === 'contractorInvoice') {
4486
+ if (operation === 'create') {
4487
+ const contractorId = this.getNodeParameter('contractorIdInvoice', i);
4488
+ const invoiceType = this.getNodeParameter('invoiceType', i);
4489
+ const invoiceNumber = this.getNodeParameter('invoiceNumber', i);
4490
+ const amount = this.getNodeParameter('invoiceAmount', i);
4491
+ const issueDate = this.getNodeParameter('invoiceIssueDate', i);
4492
+ const dueDate = this.getNodeParameter('invoiceDueDate', i);
4493
+ const maintenanceRequestId = this.getNodeParameter('maintenanceRequestIdInvoice', i);
4494
+ const filePath = this.getNodeParameter('invoiceFilePath', i);
4495
+ const ocrData = this.getNodeParameter('invoiceOcrData', i);
4496
+ const notes = this.getNodeParameter('invoiceNotes', i);
4497
+ const body = {
4498
+ contractor_id: contractorId,
4499
+ invoice_type: invoiceType,
4500
+ invoice_number: invoiceNumber,
4501
+ amount,
4502
+ issue_date: issueDate,
4503
+ };
4504
+ if (dueDate)
4505
+ body.due_date = dueDate;
4506
+ if (maintenanceRequestId)
4507
+ body.maintenance_request_id = maintenanceRequestId;
4508
+ if (filePath)
4509
+ body.file_path = filePath;
4510
+ if (ocrData && ocrData !== '{}')
4511
+ body.ocr_data = JSON.parse(ocrData);
4512
+ if (notes)
4513
+ body.notes = notes;
4514
+ responseData = await this.helpers.httpRequest({
4515
+ method: 'POST',
4516
+ url: `${baseUrl}/api/v1/contractor-invoices`,
4517
+ headers: {
4518
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4519
+ 'Accept': 'application/json',
4520
+ 'Content-Type': 'application/json',
4521
+ },
4522
+ body,
4523
+ json: true,
4524
+ });
4525
+ }
4526
+ else if (operation === 'list') {
4527
+ const contractorFilter = this.getNodeParameter('invoiceContractorFilter', i);
4528
+ const statusFilter = this.getNodeParameter('invoiceStatusFilter', i);
4529
+ const typeFilter = this.getNodeParameter('invoiceTypeFilter', i);
4530
+ let url = `${baseUrl}/api/v1/contractor-invoices`;
4531
+ const params = [];
4532
+ if (contractorFilter)
4533
+ params.push(`contractor_id=${contractorFilter}`);
4534
+ if (statusFilter)
4535
+ params.push(`status=${statusFilter}`);
4536
+ if (typeFilter)
4537
+ params.push(`invoice_type=${typeFilter}`);
4538
+ if (params.length > 0)
4539
+ url += `?${params.join('&')}`;
4540
+ responseData = await this.helpers.httpRequest({
4541
+ method: 'GET',
4542
+ url,
4543
+ headers: {
4544
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4545
+ 'Accept': 'application/json',
4546
+ },
4547
+ json: true,
4548
+ });
4549
+ }
4550
+ else if (operation === 'get') {
4551
+ const invoiceId = this.getNodeParameter('contractorInvoiceId', i);
4552
+ responseData = await this.helpers.httpRequest({
4553
+ method: 'GET',
4554
+ url: `${baseUrl}/api/v1/contractor-invoices/${invoiceId}`,
4555
+ headers: {
4556
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4557
+ 'Accept': 'application/json',
4558
+ },
4559
+ json: true,
4560
+ });
4561
+ }
4562
+ else if (operation === 'approve') {
4563
+ const invoiceId = this.getNodeParameter('contractorInvoiceId', i);
4564
+ responseData = await this.helpers.httpRequest({
4565
+ method: 'POST',
4566
+ url: `${baseUrl}/api/v1/contractor-invoices/${invoiceId}/approve`,
4567
+ headers: {
4568
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4569
+ 'Accept': 'application/json',
4570
+ 'Content-Type': 'application/json',
4571
+ },
4572
+ body: {},
4573
+ json: true,
4574
+ });
4575
+ }
4576
+ else if (operation === 'reject') {
4577
+ const invoiceId = this.getNodeParameter('contractorInvoiceId', i);
4578
+ const rejectionReason = this.getNodeParameter('invoiceRejectionReason', i);
4579
+ responseData = await this.helpers.httpRequest({
4580
+ method: 'POST',
4581
+ url: `${baseUrl}/api/v1/contractor-invoices/${invoiceId}/reject`,
4582
+ headers: {
4583
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4584
+ 'Accept': 'application/json',
4585
+ 'Content-Type': 'application/json',
4586
+ },
4587
+ body: { rejection_reason: rejectionReason },
4588
+ json: true,
4589
+ });
4590
+ }
4591
+ else if (operation === 'markPaid') {
4592
+ const invoiceId = this.getNodeParameter('contractorInvoiceId', i);
4593
+ const paymentReference = this.getNodeParameter('invoicePaymentReference', i);
4594
+ const paidAt = this.getNodeParameter('invoicePaidAt', i);
4595
+ const body = {};
4596
+ if (paymentReference)
4597
+ body.payment_reference = paymentReference;
4598
+ if (paidAt)
4599
+ body.paid_at = paidAt;
4600
+ responseData = await this.helpers.httpRequest({
4601
+ method: 'POST',
4602
+ url: `${baseUrl}/api/v1/contractor-invoices/${invoiceId}/mark-paid`,
4603
+ headers: {
4604
+ 'Authorization': `Bearer ${credentials.apiKey}`,
4605
+ 'Accept': 'application/json',
4606
+ 'Content-Type': 'application/json',
4607
+ },
4608
+ body,
4609
+ json: true,
4610
+ });
4611
+ }
4612
+ }
4613
+ // ============================================
4614
+ // PAYMENT PROOF RESOURCE (V2.3.0)
4615
+ // ============================================
4616
+ else if (resource === 'paymentProof') {
4617
+ if (operation === 'process') {
4618
+ const tenantId = this.getNodeParameter('ppTenantId', i);
4619
+ const ocrData = this.getNodeParameter('ppOcrData', i);
4620
+ const fileUrl = this.getNodeParameter('ppFileUrl', i, '');
4621
+ const body = {
4622
+ tenant_id: tenantId,
4623
+ ocr_data: ocrData,
4624
+ };
4625
+ if (fileUrl)
4626
+ body.file_url = fileUrl;
4627
+ responseData = await this.helpers.httpRequest({
4628
+ method: 'POST',
4629
+ url: `${baseUrl}/api/v1/payments/verify-proof`,
4630
+ body,
4631
+ json: true,
4632
+ });
4633
+ }
4634
+ }
4635
+ // ============================================
4636
+ // AUTOMATION RESOURCE (V2.3.0)
4637
+ // ============================================
4638
+ else if (resource === 'automation') {
4639
+ if (operation === 'logExecution') {
4640
+ const workflowName = this.getNodeParameter('autoWorkflowName', i);
4641
+ const executionId = this.getNodeParameter('autoExecutionId', i);
4642
+ const status = this.getNodeParameter('autoStatus', i);
4643
+ const documentType = this.getNodeParameter('autoDocumentType', i, '');
4644
+ const metadata = this.getNodeParameter('autoMetadata', i, '{}');
4645
+ const body = {
4646
+ workflow_name: workflowName,
4647
+ execution_id: executionId,
4648
+ status,
4649
+ metadata,
4650
+ };
4651
+ if (documentType)
4652
+ body.document_type = documentType;
4653
+ responseData = await this.helpers.httpRequest({
4654
+ method: 'POST',
4655
+ url: `${baseUrl}/api/v1/automation/log`,
4656
+ body,
4657
+ json: true,
4658
+ });
4659
+ }
4660
+ }
4661
+ // ============================================
4662
+ // POLICY RESOURCE (V2.3.0)
4663
+ // ============================================
4664
+ else if (resource === 'policy') {
4665
+ if (operation === 'evaluate') {
4666
+ const policyType = this.getNodeParameter('policyType', i);
4667
+ const amount = this.getNodeParameter('policyAmount', i);
4668
+ const documentData = this.getNodeParameter('policyDocumentData', i);
4669
+ const body = {
4670
+ policy_type: policyType,
4671
+ amount,
4672
+ document_data: documentData,
4673
+ };
4674
+ responseData = await this.helpers.httpRequest({
4675
+ method: 'POST',
4676
+ url: `${baseUrl}/api/v1/policies/evaluate`,
4677
+ body,
4678
+ json: true,
4679
+ });
4680
+ }
4681
+ }
4682
+ // ============================================
4683
+ // NOTIFICATION RESOURCE (V2.3.0)
4684
+ // ============================================
4685
+ else if (resource === 'notification') {
4686
+ if (operation === 'send') {
4687
+ const recipientType = this.getNodeParameter('notifRecipientType', i);
4688
+ const recipientId = this.getNodeParameter('notifRecipientId', i);
4689
+ const channel = this.getNodeParameter('notifChannel', i);
4690
+ const templateKey = this.getNodeParameter('notifTemplateKey', i);
4691
+ const variables = this.getNodeParameter('notifVariables', i, '{}');
4692
+ const body = {
4693
+ recipient_type: recipientType,
4694
+ recipient_id: recipientId,
4695
+ channel,
4696
+ template_key: templateKey,
4697
+ variables,
4698
+ };
4699
+ responseData = await this.helpers.httpRequest({
4700
+ method: 'POST',
4701
+ url: `${baseUrl}/api/v1/notifications/send`,
4702
+ body,
4703
+ json: true,
4704
+ });
4705
+ }
4706
+ }
3402
4707
  // Formatear respuesta
3403
4708
  const executionData = this.helpers.constructExecutionMetaData(this.helpers.returnJsonArray(responseData), { itemData: { item: i } });
3404
4709
  returnData.push(...executionData);