@ixiam/n8n-nodes-civicrm 0.2.3 → 0.3.4

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.
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ dist/
package/.eslintrc.cjs ADDED
@@ -0,0 +1,10 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ plugins: ['@typescript-eslint','n8n-nodes-base'],
5
+ extends: [
6
+ 'plugin:@typescript-eslint/recommended',
7
+ 'plugin:n8n-nodes-base/community'
8
+ ],
9
+ ignorePatterns: ['dist/**'],
10
+ };
@@ -0,0 +1,61 @@
1
+ import type {
2
+ ICredentialType,
3
+ INodeProperties,
4
+ IHttpRequestMethods,
5
+ } from 'n8n-workflow';
6
+
7
+ export class CiviCrmApi implements ICredentialType {
8
+ name = 'civiCrmApi';
9
+ displayName = 'CiviCRM API';
10
+ documentationUrl = 'https://docs.civicrm.org/dev/en/latest/api/v4/';
11
+
12
+ authenticate = {
13
+ type: 'generic' as const,
14
+ properties: {
15
+ headers: {
16
+ 'X-Civi-Auth': '={{"Bearer " + $credentials.apiToken}}',
17
+ 'Content-Type': 'application/json',
18
+ },
19
+ },
20
+ };
21
+
22
+ // Button "Test" in credential UI
23
+ test = {
24
+ request: {
25
+ // IMPORTANT: method must be typed as IHttpRequestMethods
26
+ method: 'POST' as IHttpRequestMethods,
27
+ url: '={{$credentials.baseUrl.replace(/\\/$/, "")}}/civicrm/ajax/api4/Contact/get',
28
+ headers: {
29
+ 'X-Civi-Auth': '={{"Bearer " + $credentials.apiToken}}',
30
+ 'Content-Type': 'application/json',
31
+ },
32
+ body: {
33
+ entity: 'Contact',
34
+ action: 'get',
35
+ params: { limit: 1 },
36
+ },
37
+ json: true,
38
+ },
39
+ };
40
+
41
+ properties: INodeProperties[] = [
42
+ {
43
+ displayName: 'Base URL',
44
+ name: 'baseUrl',
45
+ type: 'string',
46
+ default: '',
47
+ required: true,
48
+ placeholder: 'https://crm.example.org',
49
+ description: 'Sin la barra final',
50
+ },
51
+ {
52
+ displayName: 'API Token',
53
+ name: 'apiToken',
54
+ type: 'string',
55
+ typeOptions: { password: true },
56
+ default: '',
57
+ required: true,
58
+ description: 'Se envía como X-Civi-Auth: Bearer <token>',
59
+ },
60
+ ];
61
+ }
@@ -11,3 +11,4 @@ export declare class CiviCrm implements INodeType {
11
11
  };
12
12
  execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]>;
13
13
  }
14
+ export default CiviCrm;
@@ -10,6 +10,11 @@ const ENTITY_MAP = {
10
10
  case: 'Case',
11
11
  contribution: 'Contribution',
12
12
  membership: 'Membership',
13
+ participant: 'Participant',
14
+ group: 'Group',
15
+ relationship: 'Relationship',
16
+ email: 'Email',
17
+ activity: 'Activity',
13
18
  };
14
19
  /**
15
20
  * Nodo principal CiviCRM para n8n
@@ -22,7 +27,7 @@ class CiviCrm {
22
27
  icon: 'file:civicrm.svg',
23
28
  group: ['transform'],
24
29
  version: 1,
25
- description: 'Interact with CiviCRM API v4',
30
+ description: 'Interact with CiviCRM API v4 (Civi-Go compatible)',
26
31
  defaults: { name: 'CiviCRM' },
27
32
  inputs: ['main'],
28
33
  outputs: ['main'],
@@ -52,12 +57,15 @@ class CiviCrm {
52
57
  url,
53
58
  headers: {
54
59
  'X-Civi-Auth': `Bearer ${apiToken}`,
55
- 'Content-Type': 'application/json',
60
+ 'Content-Type': 'application/x-www-form-urlencoded',
56
61
  },
57
- body: (0, GenericFunctions_1.api4)('OptionValue', 'get', { limit: 5 }),
58
- json: true,
62
+ body: {
63
+ params: JSON.stringify({ limit: 5 }),
64
+ },
65
+ json: false,
59
66
  });
60
- const values = (res?.values || []);
67
+ const parsed = JSON.parse(res);
68
+ const values = (parsed?.values || []);
61
69
  return values.map((v) => ({
62
70
  name: v.label,
63
71
  value: v.id,
@@ -71,37 +79,20 @@ class CiviCrm {
71
79
  const out = [];
72
80
  const resource = this.getNodeParameter('resource', 0);
73
81
  const operation = this.getNodeParameter('operation', 0);
82
+ const entity = ENTITY_MAP[resource];
74
83
  for (let i = 0; i < items.length; i++) {
75
- const entity = ENTITY_MAP[resource];
76
- // GET
84
+ // === GET ===
77
85
  if (operation === 'get') {
78
86
  const id = this.getNodeParameter('id', i);
79
87
  const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, (0, GenericFunctions_1.api4)(entity, 'get', { where: [['id', '=', id]], limit: 1 }));
80
88
  out.push({ json: (res?.values?.[0] ?? {}) });
81
89
  }
82
- // GET MANY
90
+ // === GET MANY ===
83
91
  if (operation === 'getMany') {
84
92
  const returnAll = this.getNodeParameter('returnAll', i, false);
85
93
  const limit = this.getNodeParameter('limit', i, 100);
86
94
  const whereJson = this.getNodeParameter('whereJson', i, '');
87
- let where;
88
- if (whereJson) {
89
- try {
90
- where = JSON.parse(whereJson);
91
- }
92
- catch (e) {
93
- throw new Error('Where (JSON) debe ser JSON válido: usa un objeto o un array de condiciones.');
94
- }
95
- }
96
- // Normalize where: allow object or array; API4 accepts both
97
- if (where && typeof where === 'object' && !Array.isArray(where)) {
98
- where = where;
99
- }
100
- if (Array.isArray(where)) {
101
- // leave as-is; expected [[field, op, value], ...]
102
- }
103
- // Cap limit to a reasonable page size
104
- const cappedLimit = Math.max(1, Math.min(limit, 500));
95
+ const where = whereJson ? JSON.parse(whereJson) : undefined;
105
96
  if (returnAll) {
106
97
  let offset = 0;
107
98
  const page = 500;
@@ -116,13 +107,13 @@ class CiviCrm {
116
107
  }
117
108
  }
118
109
  else {
119
- const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, (0, GenericFunctions_1.api4)(entity, 'get', { where, limit: cappedLimit }));
110
+ const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/get`, (0, GenericFunctions_1.api4)(entity, 'get', { where, limit }));
120
111
  const vals = (res?.values ?? []);
121
112
  for (const v of vals)
122
113
  out.push({ json: v });
123
114
  }
124
115
  }
125
- // CREATE
116
+ // === CREATE ===
126
117
  if (operation === 'create') {
127
118
  const pairs = this.getNodeParameter('fields.field', i, []);
128
119
  const params = Object.fromEntries(pairs
@@ -131,7 +122,7 @@ class CiviCrm {
131
122
  const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/create`, (0, GenericFunctions_1.api4)(entity, 'create', params));
132
123
  out.push({ json: res });
133
124
  }
134
- // UPDATE
125
+ // === UPDATE ===
135
126
  if (operation === 'update') {
136
127
  const id = this.getNodeParameter('id', i);
137
128
  const pairs = this.getNodeParameter('fields.field', i, []);
@@ -144,7 +135,7 @@ class CiviCrm {
144
135
  const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/update`, (0, GenericFunctions_1.api4)(entity, 'update', params));
145
136
  out.push({ json: res });
146
137
  }
147
- // DELETE
138
+ // === DELETE ===
148
139
  if (operation === 'delete') {
149
140
  const id = this.getNodeParameter('id', i);
150
141
  const res = await GenericFunctions_1.civicrmApiRequest.call(this, 'POST', `/civicrm/ajax/api4/${entity}/delete`, (0, GenericFunctions_1.api4)(entity, 'delete', { id }));
@@ -176,3 +167,5 @@ function convertValue(val) {
176
167
  catch { }
177
168
  return val;
178
169
  }
170
+ // 👇 ESTA LÍNEA ES NECESARIA EN n8n >=1.110
171
+ exports.default = CiviCrm;
@@ -1,3 +1,3 @@
1
- import { INodeProperties } from 'n8n-workflow';
1
+ import type { INodeProperties } from 'n8n-workflow';
2
2
  export declare const resourceProp: INodeProperties;
3
3
  export declare const operationProp: INodeProperties;
@@ -1,33 +1,35 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.operationProp = exports.resourceProp = void 0;
4
- const RESOURCE_OPTIONS = [
5
- { name: 'Contact', value: 'contact' },
6
- { name: 'Event', value: 'event' },
7
- { name: 'Case', value: 'case' },
8
- { name: 'Contribution', value: 'contribution' },
9
- { name: 'Membership', value: 'membership' },
10
- ];
11
4
  exports.resourceProp = {
12
5
  displayName: 'Resource',
13
6
  name: 'resource',
14
7
  type: 'options',
15
8
  default: 'contact',
16
- options: RESOURCE_OPTIONS, // sin readonly/as const
9
+ options: [
10
+ { name: 'Contact', value: 'contact' },
11
+ { name: 'Event', value: 'event' },
12
+ { name: 'Case', value: 'case' },
13
+ { name: 'Contribution', value: 'contribution' },
14
+ { name: 'Membership', value: 'membership' },
15
+ { name: 'Participant', value: 'participant' },
16
+ { name: 'Group', value: 'group' },
17
+ { name: 'Relationship', value: 'relationship' },
18
+ { name: 'Email', value: 'email' },
19
+ { name: 'Activity', value: 'activity' },
20
+ { name: 'Custom API Call', value: 'customApi' },
21
+ ],
17
22
  };
18
23
  exports.operationProp = {
19
24
  displayName: 'Operation',
20
25
  name: 'operation',
21
26
  type: 'options',
22
- displayOptions: {
23
- show: { resource: ['contact', 'event', 'case', 'contribution', 'membership'] },
24
- },
25
- default: 'get',
27
+ default: 'getMany',
26
28
  options: [
29
+ { name: 'Create', value: 'create' },
30
+ { name: 'Delete', value: 'delete' },
27
31
  { name: 'Get', value: 'get' },
28
32
  { name: 'Get Many', value: 'getMany' },
29
- { name: 'Create', value: 'create' },
30
33
  { name: 'Update', value: 'update' },
31
- { name: 'Delete', value: 'delete' },
32
- ], // tipo mutable
34
+ ],
33
35
  };
@@ -1,13 +1,10 @@
1
- import { type IExecuteFunctions } from 'n8n-workflow';
1
+ import type { IExecuteFunctions } from 'n8n-workflow';
2
2
  /**
3
- * Ejecuta una llamada a la API de CiviCRM v4
3
+ * Ejecuta una llamada a la API de CiviCRM v4 (form-urlencoded, formato Civi-Go)
4
4
  */
5
5
  export declare function civicrmApiRequest(this: IExecuteFunctions, method: 'POST', path: string, body: Record<string, unknown>): Promise<any>;
6
6
  /**
7
- * Devuelve el formato estándar para una llamada API4
7
+ * Devuelve el formato esperado por Civi-Go
8
+ * Solo los parámetros planos: where, limit, select...
8
9
  */
9
- export declare function api4(entity: string, action: string, params?: Record<string, unknown>): {
10
- entity: string;
11
- action: string;
12
- params: Record<string, unknown>;
13
- };
10
+ export declare function api4(entity: string, action: string, params?: Record<string, unknown>): Record<string, unknown>;
@@ -3,25 +3,25 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.api4 = exports.civicrmApiRequest = void 0;
4
4
  const n8n_workflow_1 = require("n8n-workflow");
5
5
  /**
6
- * Ejecuta una llamada a la API de CiviCRM v4
6
+ * Ejecuta una llamada a la API de CiviCRM v4 (form-urlencoded, formato Civi-Go)
7
7
  */
8
8
  async function civicrmApiRequest(method, path, body) {
9
9
  const { baseUrl, apiToken } = (await this.getCredentials('civiCrmApi'));
10
10
  const options = {
11
11
  method,
12
- url: `${baseUrl}${path}`,
12
+ url: `${baseUrl.replace(/\/$/, '')}${path}`,
13
13
  headers: {
14
- 'Content-Type': 'application/json',
14
+ 'Content-Type': 'application/x-www-form-urlencoded',
15
15
  'X-Civi-Auth': `Bearer ${apiToken}`,
16
16
  },
17
- body,
18
- json: true,
17
+ // Civi-Go exige el campo 'params' con el JSON serializado
18
+ body: {
19
+ params: JSON.stringify(body),
20
+ },
21
+ json: true, // importante: dejar que n8n maneje el parseo
19
22
  };
20
23
  try {
21
24
  const response = await this.helpers.httpRequest(options);
22
- if (response?.is_error) {
23
- throw new n8n_workflow_1.NodeApiError(this.getNode(), response);
24
- }
25
25
  return response;
26
26
  }
27
27
  catch (error) {
@@ -30,9 +30,10 @@ async function civicrmApiRequest(method, path, body) {
30
30
  }
31
31
  exports.civicrmApiRequest = civicrmApiRequest;
32
32
  /**
33
- * Devuelve el formato estándar para una llamada API4
33
+ * Devuelve el formato esperado por Civi-Go
34
+ * Solo los parámetros planos: where, limit, select...
34
35
  */
35
36
  function api4(entity, action, params = {}) {
36
- return { entity, action, params };
37
+ return params;
37
38
  }
38
39
  exports.api4 = api4;
@@ -0,0 +1,243 @@
1
+ import type {
2
+ IExecuteFunctions,
3
+ ILoadOptionsFunctions,
4
+ INodeExecutionData,
5
+ INodePropertyOptions,
6
+ INodeType,
7
+ INodeTypeDescription,
8
+ IDataObject,
9
+ } from 'n8n-workflow';
10
+
11
+ import { civicrmApiRequest, api4 } from '../transport/GenericFunctions';
12
+ import { resourceProp, operationProp } from './descriptions/resources';
13
+ import { genericFields, upsertFields } from './descriptions/generic';
14
+
15
+ /**
16
+ * Recursos soportados
17
+ */
18
+ type Resource =
19
+ | 'contact'
20
+ | 'event'
21
+ | 'case'
22
+ | 'contribution'
23
+ | 'membership'
24
+ | 'participant'
25
+ | 'group'
26
+ | 'relationship'
27
+ | 'email'
28
+ | 'activity';
29
+
30
+ const ENTITY_MAP: Record<Resource, string> = {
31
+ contact: 'Contact',
32
+ event: 'Event',
33
+ case: 'Case',
34
+ contribution: 'Contribution',
35
+ membership: 'Membership',
36
+ participant: 'Participant',
37
+ group: 'Group',
38
+ relationship: 'Relationship',
39
+ email: 'Email',
40
+ activity: 'Activity',
41
+ };
42
+
43
+
44
+ type Operation = 'get' | 'getMany' | 'create' | 'update' | 'delete';
45
+
46
+ /**
47
+ * Nodo principal CiviCRM para n8n
48
+ */
49
+ export class CiviCrm implements INodeType {
50
+ description: INodeTypeDescription = {
51
+ displayName: 'CiviCRM',
52
+ name: 'civiCrm',
53
+ icon: 'file:civicrm.svg',
54
+ group: ['transform'],
55
+ version: 1,
56
+ description: 'Interact with CiviCRM API v4 (Civi-Go compatible)',
57
+ defaults: { name: 'CiviCRM' },
58
+ inputs: ['main'],
59
+ outputs: ['main'],
60
+ credentials: [{ name: 'civiCrmApi', required: true }],
61
+ properties: [
62
+ resourceProp,
63
+ operationProp,
64
+ {
65
+ displayName: 'ID',
66
+ name: 'id',
67
+ type: 'number',
68
+ default: 0,
69
+ required: true,
70
+ displayOptions: { show: { operation: ['get', 'update', 'delete'] } },
71
+ },
72
+ ...genericFields,
73
+ ...upsertFields,
74
+ ],
75
+ };
76
+
77
+ methods = {
78
+ loadOptions: {
79
+ async getOptionValues(this: ILoadOptionsFunctions) {
80
+ const { baseUrl, apiToken } = (await this.getCredentials('civiCrmApi')) as {
81
+ baseUrl: string;
82
+ apiToken: string;
83
+ };
84
+
85
+ const url = `${baseUrl.replace(/\/$/, '')}/civicrm/ajax/api4/OptionValue/get`;
86
+
87
+ const res = await this.helpers.httpRequest({
88
+ method: 'POST',
89
+ url,
90
+ headers: {
91
+ 'X-Civi-Auth': `Bearer ${apiToken}`,
92
+ 'Content-Type': 'application/x-www-form-urlencoded',
93
+ },
94
+ body: {
95
+ params: JSON.stringify({ limit: 5 }),
96
+ },
97
+ json: false,
98
+ });
99
+
100
+ const parsed = JSON.parse(res);
101
+ const values = (parsed?.values || []) as Array<{ id: number; label: string }>;
102
+ return values.map((v): INodePropertyOptions => ({
103
+ name: v.label,
104
+ value: v.id,
105
+ }));
106
+ },
107
+ },
108
+ };
109
+
110
+ async execute(this: IExecuteFunctions) {
111
+ const items = this.getInputData();
112
+ const out: INodeExecutionData[] = [];
113
+
114
+ const resource = this.getNodeParameter('resource', 0) as Resource;
115
+ const operation = this.getNodeParameter('operation', 0) as Operation;
116
+ const entity = ENTITY_MAP[resource];
117
+
118
+ for (let i = 0; i < items.length; i++) {
119
+ // === GET ===
120
+ if (operation === 'get') {
121
+ const id = this.getNodeParameter('id', i) as number;
122
+ const res = await civicrmApiRequest.call(
123
+ this,
124
+ 'POST',
125
+ `/civicrm/ajax/api4/${entity}/get`,
126
+ api4(entity, 'get', { where: [['id', '=', id]], limit: 1 }),
127
+ );
128
+ out.push({ json: (res?.values?.[0] ?? {}) as IDataObject });
129
+ }
130
+
131
+ // === GET MANY ===
132
+ if (operation === 'getMany') {
133
+ const returnAll = this.getNodeParameter('returnAll', i, false) as boolean;
134
+ const limit = this.getNodeParameter('limit', i, 100) as number;
135
+ const whereJson = this.getNodeParameter('whereJson', i, '') as string;
136
+ const where = whereJson ? JSON.parse(whereJson) : undefined;
137
+
138
+ if (returnAll) {
139
+ let offset = 0;
140
+ const page = 500;
141
+ while (true) {
142
+ const res = await civicrmApiRequest.call(
143
+ this,
144
+ 'POST',
145
+ `/civicrm/ajax/api4/${entity}/get`,
146
+ api4(entity, 'get', { where, limit: page, offset }),
147
+ );
148
+ const vals = (res?.values ?? []) as IDataObject[];
149
+ for (const v of vals) out.push({ json: v });
150
+ if (vals.length < page) break;
151
+ offset += page;
152
+ }
153
+ } else {
154
+ const res = await civicrmApiRequest.call(
155
+ this,
156
+ 'POST',
157
+ `/civicrm/ajax/api4/${entity}/get`,
158
+ api4(entity, 'get', { where, limit }),
159
+ );
160
+ const vals = (res?.values ?? []) as IDataObject[];
161
+ for (const v of vals) out.push({ json: v });
162
+ }
163
+ }
164
+
165
+ // === CREATE ===
166
+ if (operation === 'create') {
167
+ const pairs = this.getNodeParameter('fields.field', i, []) as Array<{
168
+ fieldName: string;
169
+ fieldValue: string;
170
+ }>;
171
+ const params = Object.fromEntries(
172
+ pairs
173
+ .filter((p) => p.fieldName)
174
+ .map((p) => [p.fieldName, convertValue(p.fieldValue)]),
175
+ );
176
+ const res = await civicrmApiRequest.call(
177
+ this,
178
+ 'POST',
179
+ `/civicrm/ajax/api4/${entity}/create`,
180
+ api4(entity, 'create', params),
181
+ );
182
+ out.push({ json: res as IDataObject });
183
+ }
184
+
185
+ // === UPDATE ===
186
+ if (operation === 'update') {
187
+ const id = this.getNodeParameter('id', i) as number;
188
+ const pairs = this.getNodeParameter('fields.field', i, []) as Array<{
189
+ fieldName: string;
190
+ fieldValue: string;
191
+ }>;
192
+ const params = {
193
+ id,
194
+ ...Object.fromEntries(
195
+ pairs
196
+ .filter((p) => p.fieldName)
197
+ .map((p) => [p.fieldName, convertValue(p.fieldValue)]),
198
+ ),
199
+ };
200
+ const res = await civicrmApiRequest.call(
201
+ this,
202
+ 'POST',
203
+ `/civicrm/ajax/api4/${entity}/update`,
204
+ api4(entity, 'update', params),
205
+ );
206
+ out.push({ json: res as IDataObject });
207
+ }
208
+
209
+ // === DELETE ===
210
+ if (operation === 'delete') {
211
+ const id = this.getNodeParameter('id', i) as number;
212
+ const res = await civicrmApiRequest.call(
213
+ this,
214
+ 'POST',
215
+ `/civicrm/ajax/api4/${entity}/delete`,
216
+ api4(entity, 'delete', { id }),
217
+ );
218
+ out.push({ json: res as IDataObject });
219
+ }
220
+ }
221
+
222
+ return [out];
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Convierte string a número, boolean, objeto o deja string
228
+ */
229
+ function convertValue(val: string): unknown {
230
+ const t = String(val ?? '').trim();
231
+ if (t === '') return '';
232
+ if (t === 'true') return true;
233
+ if (t === 'false') return false;
234
+ if (/^-?\d+(\.\d+)?$/.test(t)) return Number(t);
235
+ try {
236
+ const j = JSON.parse(t);
237
+ if (typeof j === 'object') return j;
238
+ } catch {}
239
+ return val;
240
+ }
241
+
242
+ // 👇 ESTA LÍNEA ES NECESARIA EN n8n >=1.110
243
+ export default CiviCrm;
@@ -0,0 +1,28 @@
1
+ <svg xml:space="preserve" style="max-height: 500px" viewBox="190.4982463465553 39.1683 70.07818371607516 70.33139999999999" y="0px" x="0px" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" id="Layer_1" version="1.1" width="70.07818371607516" height="70.33139999999999">
2
+ <style type="text/css">
3
+ .st0{fill:#086287;}
4
+ .st1{fill:#81C459;}
5
+ </style>
6
+ <g>
7
+ <g>
8
+ <g>
9
+ <path d="M216.26,104.26C216.26,104.26,216.26,104.26,216.26,104.26c-3.77-0.07-5.2-4.43-5.35-4.92l-12.85-42.11&#xA;&#9;&#9;&#9;&#9;c-0.76-2.47-0.58-4.46,0.51-5.92c0.71-0.94,2.12-2.04,4.83-1.99c0.99,0.02,1.77,0.19,1.85,0.21l42.78,9.97&#xA;&#9;&#9;&#9;&#9;c2.75,0.64,4.44,1.94,5.02,3.85c0.93,3.04-1.65,5.92-1.95,6.24l-29.94,32.12C219.57,103.43,217.91,104.29,216.26,104.26z&#xA;&#9;&#9;&#9;&#9; M203.19,53.73c-0.77-0.01-1.02,0.19-1.06,0.26c-0.22,0.31-0.26,0.44,0.19,1.93l12.85,42.11c0.29,0.9,0.76,1.74,1.18,1.77l0,0&#xA;&#9;&#9;&#9;&#9;c0.01,0,0.52,0,1.57-1.13l29.94-32.12c0.55-0.61,1.06-1.6,0.95-1.9c-0.08-0.22-0.43-0.5-1.77-0.81l-42.78-9.97l0,0&#xA;&#9;&#9;&#9;&#9;C204.24,53.87,203.7,53.74,203.19,53.73z" class="st0"/>
10
+ </g>
11
+ <g>
12
+ <path d="M211.45,100.83c-1.38,0.51-2.83,0.61-3.96,0.24c0,0,0,0,0,0c-3.57-1.17-3.68-5.74-3.68-6.26l0-41l4.21,0.94&#xA;&#9;&#9;&#9;&#9;l0.02,40.15c0.01,0.94,0.25,1.84,0.83,1.94l0,0c0,0,0.3,0.09,1.15-0.3L211.45,100.83z M214.02,94.31l34.62-20.03&#xA;&#9;&#9;&#9;&#9;c0.7-0.42,1.53-1.04,1.46-1.54c-0.01-0.07-0.17-0.52-0.92-1.05l2.9-3.3c1.64,1.25,2.47,2.71,2.47,4.35c0,3.17-3.3,5.17-3.68,5.39&#xA;&#9;&#9;&#9;&#9;l-35.51,20.4L214.02,94.31z M245.14,69.42l-34.02-19.79c0,0-0.85-0.54-1.34-0.7c-0.73-0.24-1.08,0-1.15,0.05&#xA;&#9;&#9;&#9;&#9;c-0.2,0.14-0.27,0.24-0.35,1.24l-4.38-0.82c0.24-1.82,0.95-3.17,2.12-4.03c0.95-0.69,2.62-1.33,5.19-0.5&#xA;&#9;&#9;&#9;&#9;c0.94,0.31,1.63,0.7,1.71,0.74l35.39,20.42L245.14,69.42z" class="st1"/>
13
+ </g>
14
+ </g>
15
+ <g>
16
+ <path d="M183.02,66.82c-0.48-0.28-0.86-0.67-1.15-1.15c-0.28-0.48-0.42-1-0.42-1.57c0-0.56,0.14-1.09,0.42-1.57&#xA;&#9;&#9;&#9;s0.66-0.86,1.15-1.15s1-0.42,1.57-0.42s1.09,0.14,1.57,0.42s0.86,0.66,1.15,1.15s0.42,1,0.42,1.57c0,0.57-0.14,1.09-0.42,1.57&#xA;&#9;&#9;&#9;c-0.28,0.48-0.66,0.86-1.15,1.15s-1,0.42-1.57,0.42S183.5,67.1,183.02,66.82z M185.93,66.42c0.41-0.24,0.74-0.57,0.98-0.98&#xA;&#9;&#9;&#9;c0.24-0.41,0.36-0.86,0.36-1.35c0-0.48-0.12-0.93-0.36-1.34c-0.24-0.41-0.57-0.73-0.98-0.98c-0.41-0.24-0.86-0.36-1.35-0.36&#xA;&#9;&#9;&#9;c-0.48,0-0.93,0.12-1.34,0.36c-0.41,0.24-0.73,0.57-0.98,0.98c-0.24,0.41-0.36,0.85-0.36,1.34c0,0.49,0.12,0.94,0.36,1.35&#xA;&#9;&#9;&#9;c0.24,0.41,0.57,0.74,0.98,0.98s0.85,0.36,1.34,0.36C185.07,66.79,185.52,66.66,185.93,66.42z M183.37,65.8&#xA;&#9;&#9;&#9;c-0.06-0.05-0.08-0.12-0.08-0.21v-2.83c0-0.11,0.04-0.21,0.11-0.28s0.17-0.12,0.28-0.12h1.05c0.46,0,0.8,0.1,1.03,0.29&#xA;&#9;&#9;&#9;s0.34,0.45,0.34,0.76c0,0.21-0.06,0.39-0.18,0.53c-0.12,0.14-0.28,0.25-0.46,0.32c0.16,0.06,0.3,0.18,0.42,0.35&#xA;&#9;&#9;&#9;c0.12,0.18,0.18,0.42,0.18,0.74v0.24c0,0.08-0.03,0.15-0.09,0.21c-0.06,0.06-0.13,0.09-0.21,0.09s-0.15-0.03-0.21-0.09&#xA;&#9;&#9;&#9;c-0.06-0.06-0.09-0.13-0.09-0.21v-0.16c0-0.25-0.04-0.46-0.11-0.63c-0.07-0.17-0.25-0.26-0.53-0.26h-0.95v1.06&#xA;&#9;&#9;&#9;c0,0.08-0.03,0.15-0.09,0.21c-0.06,0.06-0.13,0.08-0.21,0.08C183.49,65.88,183.42,65.86,183.37,65.8z M184.7,64.02&#xA;&#9;&#9;&#9;c0.25,0,0.45-0.05,0.6-0.15c0.15-0.1,0.22-0.25,0.22-0.44c0-0.18-0.06-0.32-0.19-0.42c-0.13-0.1-0.34-0.14-0.65-0.14h-0.81v1.15&#xA;&#9;&#9;&#9;H184.7z" class="st0"/>
17
+ </g>
18
+ <g>
19
+ <path d="M31.72,88.55c-2.15-1.18-3.85-2.87-5.08-5.08c-1.23-2.21-1.85-4.79-1.85-7.74s0.62-5.53,1.85-7.74&#xA;&#9;&#9;&#9;c1.23-2.21,2.93-3.9,5.08-5.08c2.15-1.18,4.58-1.77,7.29-1.77c1.84,0,3.49,0.22,4.94,0.65c1.45,0.43,2.89,1.06,4.33,1.87&#xA;&#9;&#9;&#9;c0.6,0.32,0.89,0.84,0.89,1.54c0,0.41-0.15,0.77-0.45,1.08c-0.3,0.31-0.69,0.47-1.18,0.47c-0.27,0-0.51-0.05-0.73-0.16&#xA;&#9;&#9;&#9;c-1.19-0.62-2.36-1.1-3.49-1.42c-1.14-0.32-2.45-0.49-3.94-0.49c-2.33,0-4.31,0.48-5.95,1.44c-1.64,0.96-2.87,2.28-3.7,3.94&#xA;&#9;&#9;&#9;c-0.83,1.67-1.24,3.55-1.24,5.67s0.41,4,1.24,5.67c0.83,1.67,2.06,2.98,3.7,3.94c1.64,0.96,3.62,1.44,5.95,1.44&#xA;&#9;&#9;&#9;c1.49,0,2.8-0.16,3.94-0.49c1.14-0.32,2.3-0.8,3.49-1.42c0.22-0.11,0.46-0.16,0.73-0.16c0.49,0,0.88,0.15,1.18,0.45&#xA;&#9;&#9;&#9;c0.3,0.3,0.45,0.66,0.45,1.1c0,0.7-0.3,1.22-0.89,1.54c-1.44,0.81-2.88,1.44-4.33,1.87c-1.45,0.43-3.09,0.65-4.94,0.65&#xA;&#9;&#9;&#9;C36.3,90.32,33.87,89.73,31.72,88.55z" class="st0"/>
20
+ <path d="M53.47,64.35c-0.41-0.41-0.61-0.89-0.61-1.46v-0.16c0-0.57,0.2-1.06,0.61-1.46c0.41-0.41,0.89-0.61,1.46-0.61&#xA;&#9;&#9;&#9;h0.24c0.57,0,1.06,0.2,1.46,0.61s0.61,0.89,0.61,1.46v0.16c0,0.57-0.2,1.06-0.61,1.46c-0.41,0.41-0.89,0.61-1.46,0.61h-0.24&#xA;&#9;&#9;&#9;C54.37,64.96,53.88,64.76,53.47,64.35z M53.72,89.58c-0.35-0.35-0.53-0.79-0.53-1.3V70.86c0-0.51,0.18-0.96,0.53-1.32&#xA;&#9;&#9;&#9;c0.35-0.37,0.79-0.55,1.3-0.55c0.54,0,0.99,0.18,1.34,0.53c0.35,0.35,0.53,0.8,0.53,1.34v17.43c0,0.51-0.18,0.95-0.55,1.3&#xA;&#9;&#9;&#9;c-0.37,0.35-0.81,0.53-1.32,0.53C54.5,90.11,54.07,89.94,53.72,89.58z" class="st0"/>
21
+ <path d="M68.5,89.69c-0.41-0.28-0.7-0.64-0.89-1.08l-7.07-16.9c-0.14-0.32-0.2-0.61-0.2-0.85&#xA;&#9;&#9;&#9;c0-0.51,0.18-0.96,0.53-1.32c0.35-0.37,0.79-0.55,1.3-0.55c0.35,0,0.68,0.1,1,0.3c0.31,0.2,0.53,0.45,0.67,0.75l6.22,15.76&#xA;&#9;&#9;&#9;l6.22-15.76c0.14-0.3,0.36-0.55,0.67-0.75c0.31-0.2,0.64-0.3,1-0.3c0.51,0,0.95,0.18,1.3,0.55c0.35,0.37,0.53,0.81,0.53,1.32&#xA;&#9;&#9;&#9;c0,0.24-0.07,0.53-0.2,0.85l-7.07,16.9c-0.19,0.43-0.49,0.79-0.89,1.08s-0.85,0.43-1.34,0.43h-0.41&#xA;&#9;&#9;&#9;C69.36,90.11,68.91,89.97,68.5,89.69z" class="st0"/>
22
+ <path d="M83.5,64.35c-0.41-0.41-0.61-0.89-0.61-1.46v-0.16c0-0.57,0.2-1.06,0.61-1.46c0.41-0.41,0.89-0.61,1.46-0.61&#xA;&#9;&#9;&#9;h0.24c0.57,0,1.06,0.2,1.46,0.61s0.61,0.89,0.61,1.46v0.16c0,0.57-0.2,1.06-0.61,1.46c-0.41,0.41-0.89,0.61-1.46,0.61h-0.24&#xA;&#9;&#9;&#9;C84.39,64.96,83.9,64.76,83.5,64.35z M83.74,89.58c-0.35-0.35-0.53-0.79-0.53-1.3V70.86c0-0.51,0.18-0.96,0.53-1.32&#xA;&#9;&#9;&#9;c0.35-0.37,0.79-0.55,1.3-0.55c0.54,0,0.99,0.18,1.34,0.53c0.35,0.35,0.53,0.8,0.53,1.34v17.43c0,0.51-0.18,0.95-0.55,1.3&#xA;&#9;&#9;&#9;c-0.37,0.35-0.81,0.53-1.32,0.53C84.53,90.11,84.09,89.94,83.74,89.58z" class="st0"/>
23
+ <path d="M97.65,88.55c-2.15-1.18-3.85-2.87-5.08-5.08c-1.23-2.21-1.85-4.79-1.85-7.74s0.62-5.53,1.85-7.74&#xA;&#9;&#9;&#9;c1.23-2.21,2.93-3.9,5.08-5.08c2.15-1.18,4.58-1.77,7.29-1.77c1.84,0,3.49,0.22,4.94,0.65c1.45,0.43,2.89,1.06,4.33,1.87&#xA;&#9;&#9;&#9;c0.6,0.32,0.89,0.84,0.89,1.54c0,0.41-0.15,0.77-0.45,1.08c-0.3,0.31-0.69,0.47-1.18,0.47c-0.27,0-0.51-0.05-0.73-0.16&#xA;&#9;&#9;&#9;c-1.19-0.62-2.36-1.1-3.49-1.42c-1.14-0.32-2.45-0.49-3.94-0.49c-2.33,0-4.31,0.48-5.95,1.44c-1.64,0.96-2.87,2.28-3.7,3.94&#xA;&#9;&#9;&#9;c-0.83,1.67-1.24,3.55-1.24,5.67s0.41,4,1.24,5.67c0.83,1.67,2.06,2.98,3.7,3.94c1.64,0.96,3.62,1.44,5.95,1.44&#xA;&#9;&#9;&#9;c1.49,0,2.8-0.16,3.94-0.49c1.14-0.32,2.3-0.8,3.49-1.42c0.22-0.11,0.46-0.16,0.73-0.16c0.49,0,0.88,0.15,1.18,0.45&#xA;&#9;&#9;&#9;c0.3,0.3,0.45,0.66,0.45,1.1c0,0.7-0.3,1.22-0.89,1.54c-1.44,0.81-2.88,1.44-4.33,1.87c-1.45,0.43-3.09,0.65-4.94,0.65&#xA;&#9;&#9;&#9;C102.24,90.32,99.81,89.73,97.65,88.55z" class="st0"/>
24
+ <path d="M119.9,89.58c-0.35-0.35-0.53-0.79-0.53-1.3V63.42c0-0.51,0.18-0.96,0.53-1.32s0.79-0.55,1.3-0.55h9.47&#xA;&#9;&#9;&#9;c3.68,0,6.45,0.76,8.29,2.28c1.84,1.52,2.76,3.51,2.76,5.97c0,1.62-0.45,3.11-1.36,4.45c-0.91,1.34-2.23,2.31-3.96,2.9&#xA;&#9;&#9;&#9;c1.95,0.51,3.28,1.73,4,3.64c0.72,1.91,1.08,3.79,1.08,5.63v1.87c0,0.51-0.18,0.95-0.53,1.3c-0.35,0.35-0.79,0.53-1.3,0.53&#xA;&#9;&#9;&#9;c-0.57,0-1.02-0.17-1.36-0.51c-0.34-0.34-0.51-0.78-0.51-1.32v-1.22c0-1.35-0.12-2.61-0.37-3.76s-0.83-2.18-1.75-3.09&#xA;&#9;&#9;&#9;c-0.92-0.91-2.32-1.36-4.18-1.36h-8.41v9.43c0,0.51-0.18,0.95-0.55,1.3s-0.81,0.53-1.32,0.53&#xA;&#9;&#9;&#9;C120.68,90.11,120.25,89.94,119.9,89.58z M130.42,75.37c2.33,0,4.18-0.46,5.55-1.38c1.37-0.92,2.05-2.22,2.05-3.9&#xA;&#9;&#9;&#9;c0-1.62-0.59-2.87-1.77-3.74c-1.18-0.87-3.19-1.3-6.03-1.3h-7.15v10.32H130.42z" class="st0"/>
25
+ <path d="M146.71,89.58c-0.35-0.35-0.53-0.79-0.53-1.3V64.03c0-0.73,0.26-1.36,0.77-1.89&#xA;&#9;&#9;&#9;c0.51-0.53,1.14-0.79,1.87-0.79h1.34c0.54,0,1.04,0.16,1.48,0.49c0.45,0.33,0.78,0.73,1,1.22l9.22,22.99l9.22-22.99&#xA;&#9;&#9;&#9;c0.22-0.49,0.55-0.89,1-1.22c0.45-0.32,0.94-0.49,1.48-0.49h1.34c0.73,0,1.35,0.26,1.87,0.79c0.51,0.53,0.77,1.16,0.77,1.89v24.25&#xA;&#9;&#9;&#9;c0,0.51-0.18,0.95-0.53,1.3c-0.35,0.35-0.79,0.53-1.3,0.53c-0.52,0-0.96-0.18-1.32-0.53c-0.37-0.35-0.55-0.79-0.55-1.3V65.21&#xA;&#9;&#9;&#9;l-9.38,23.2c-0.22,0.51-0.56,0.93-1.04,1.24c-0.47,0.31-1,0.47-1.56,0.47s-1.09-0.16-1.56-0.47c-0.47-0.31-0.82-0.72-1.04-1.24&#xA;&#9;&#9;&#9;l-9.38-23.2v23.08c0,0.51-0.18,0.95-0.55,1.3s-0.81,0.53-1.32,0.53C147.5,90.11,147.06,89.94,146.71,89.58z" class="st0"/>
26
+ </g>
27
+ </g>
28
+ </svg>
@@ -0,0 +1,9 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+ export const genericFields: INodeProperties[] = [
3
+ { displayName: 'Return All', name: 'returnAll', type: 'boolean', default: false, displayOptions: { show: { operation: ['getMany'] } } },
4
+ { displayName: 'Limit', name: 'limit', type: 'number', typeOptions: { minValue: 1, maxValue: 1000 }, default: 100, displayOptions: { show: { operation: ['getMany'], returnAll: [false] } } },
5
+ { displayName: 'Where (JSON)', name: 'whereJson', type: 'string', default: '', placeholder: `[["first_name","=","Alice"]]`, displayOptions: { show: { operation: ['getMany'] } } },
6
+ ];
7
+ export const upsertFields: INodeProperties[] = [
8
+ { displayName: 'Fields', name: 'fields', type: 'fixedCollection', typeOptions: { multipleValues: true }, default: {}, options: [{ name:'field', displayName:'Field', values:[{displayName:'Name',name:'fieldName',type:'string',default:''},{displayName:'Value',name:'fieldValue',type:'string',default:''}]}], displayOptions: { show: { operation: ['create','update'] } } }
9
+ ];
@@ -0,0 +1,35 @@
1
+ import type { INodeProperties } from 'n8n-workflow';
2
+
3
+ export const resourceProp: INodeProperties = {
4
+ displayName: 'Resource',
5
+ name: 'resource',
6
+ type: 'options',
7
+ default: 'contact',
8
+ options: [
9
+ { name: 'Contact', value: 'contact' },
10
+ { name: 'Event', value: 'event' },
11
+ { name: 'Case', value: 'case' },
12
+ { name: 'Contribution', value: 'contribution' },
13
+ { name: 'Membership', value: 'membership' },
14
+ { name: 'Participant', value: 'participant' },
15
+ { name: 'Group', value: 'group' },
16
+ { name: 'Relationship', value: 'relationship' },
17
+ { name: 'Email', value: 'email' },
18
+ { name: 'Activity', value: 'activity' },
19
+ { name: 'Custom API Call', value: 'customApi' },
20
+ ],
21
+ };
22
+
23
+ export const operationProp: INodeProperties = {
24
+ displayName: 'Operation',
25
+ name: 'operation',
26
+ type: 'options',
27
+ default: 'getMany',
28
+ options: [
29
+ { name: 'Create', value: 'create' },
30
+ { name: 'Delete', value: 'delete' },
31
+ { name: 'Get', value: 'get' },
32
+ { name: 'Get Many', value: 'getMany' },
33
+ { name: 'Update', value: 'update' },
34
+ ],
35
+ };
@@ -0,0 +1,50 @@
1
+ import type { IExecuteFunctions, IHttpRequestOptions, JsonObject } from 'n8n-workflow';
2
+ import { NodeApiError } from 'n8n-workflow';
3
+
4
+ /**
5
+ * Ejecuta una llamada a la API de CiviCRM v4 (form-urlencoded, formato Civi-Go)
6
+ */
7
+ export async function civicrmApiRequest(
8
+ this: IExecuteFunctions,
9
+ method: 'POST',
10
+ path: string,
11
+ body: Record<string, unknown>,
12
+ ) {
13
+ const { baseUrl, apiToken } = (await this.getCredentials('civiCrmApi')) as {
14
+ baseUrl: string;
15
+ apiToken: string;
16
+ };
17
+
18
+ const options: IHttpRequestOptions = {
19
+ method,
20
+ url: `${baseUrl.replace(/\/$/, '')}${path}`,
21
+ headers: {
22
+ 'Content-Type': 'application/x-www-form-urlencoded',
23
+ 'X-Civi-Auth': `Bearer ${apiToken}`,
24
+ },
25
+ // Civi-Go exige el campo 'params' con el JSON serializado
26
+ body: {
27
+ params: JSON.stringify(body),
28
+ },
29
+ json: true, // importante: dejar que n8n maneje el parseo
30
+ };
31
+
32
+ try {
33
+ const response = await this.helpers.httpRequest(options);
34
+ return response;
35
+ } catch (error: unknown) {
36
+ throw new NodeApiError(this.getNode(), error as JsonObject);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Devuelve el formato esperado por Civi-Go
42
+ * Solo los parámetros planos: where, limit, select...
43
+ */
44
+ export function api4(
45
+ entity: string,
46
+ action: string,
47
+ params: Record<string, unknown> = {},
48
+ ) {
49
+ return params;
50
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ixiam/n8n-nodes-civicrm",
3
- "version": "0.2.3",
4
- "description": "CiviCRM API v4 for n8n",
3
+ "version": "0.3.4",
4
+ "description": "CiviCRM API v4",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "n8n-community-node-package",
@@ -10,12 +10,6 @@
10
10
  ],
11
11
  "main": "dist/index.js",
12
12
  "types": "dist/index.d.ts",
13
- "files": [
14
- "dist",
15
- "icons",
16
- "README.md",
17
- "LICENSE"
18
- ],
19
13
  "scripts": {
20
14
  "dev": "n8n-node dev",
21
15
  "build": "n8n-node build",
@@ -30,17 +24,6 @@
30
24
  "dist/credentials/CiviCrmApi.credentials.js"
31
25
  ]
32
26
  },
33
- "publishConfig": {
34
- "access": "public"
35
- },
36
- "repository": {
37
- "type": "git",
38
- "url": "git+https://git.ixiam.com/dev/research.git"
39
- },
40
- "bugs": {
41
- "url": "https://git.ixiam.com/dev/research/issues"
42
- },
43
- "homepage": "https://git.ixiam.com/dev/research#readme",
44
27
  "devDependencies": {
45
28
  "@n8n/node-cli": "0.11.0",
46
29
  "@types/node": "^20.11.30",
package/tsconfig.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["es2020"],
4
+ "module": "commonjs",
5
+ "target": "es2020",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": ".",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "resolveJsonModule": true,
12
+ "skipLibCheck": true
13
+ },
14
+ "include": ["nodes/**/*.ts", "credentials/**/*.ts"],
15
+ "exclude": ["node_modules", "dist"]
16
+ }