constec 0.4.1__tar.gz → 0.5.0__tar.gz

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.
Files changed (43) hide show
  1. {constec-0.4.1 → constec-0.5.0}/PKG-INFO +1 -1
  2. constec-0.5.0/constec/db/migrations/0006_automation_trigger_action_executionlog_notificationtemplate.py +270 -0
  3. {constec-0.4.1 → constec-0.5.0}/constec/db/models/__init__.py +6 -1
  4. constec-0.5.0/constec/db/models/automation.py +467 -0
  5. {constec-0.4.1 → constec-0.5.0}/constec.egg-info/PKG-INFO +1 -1
  6. {constec-0.4.1 → constec-0.5.0}/constec.egg-info/SOURCES.txt +1 -0
  7. {constec-0.4.1 → constec-0.5.0}/pyproject.toml +1 -1
  8. constec-0.4.1/constec/db/models/automation.py +0 -99
  9. {constec-0.4.1 → constec-0.5.0}/LICENSE +0 -0
  10. {constec-0.4.1 → constec-0.5.0}/README.md +0 -0
  11. {constec-0.4.1 → constec-0.5.0}/constec/db/__init__.py +0 -0
  12. {constec-0.4.1 → constec-0.5.0}/constec/db/apps.py +0 -0
  13. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/0001_initial.py +0 -0
  14. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/0002_module_level.py +0 -0
  15. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/0003_remove_module_level.py +0 -0
  16. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/0004_rename_entities_company_cuit_idx_entities_company_e2c50f_idx_and_more.py +0 -0
  17. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/0005_event.py +0 -0
  18. {constec-0.4.1 → constec-0.5.0}/constec/db/migrations/__init__.py +0 -0
  19. {constec-0.4.1 → constec-0.5.0}/constec/db/models/base.py +0 -0
  20. {constec-0.4.1 → constec-0.5.0}/constec/db/models/company.py +0 -0
  21. {constec-0.4.1 → constec-0.5.0}/constec/db/models/contact.py +0 -0
  22. {constec-0.4.1 → constec-0.5.0}/constec/db/models/erp.py +0 -0
  23. {constec-0.4.1 → constec-0.5.0}/constec/db/models/erp_entity.py +0 -0
  24. {constec-0.4.1 → constec-0.5.0}/constec/db/models/flow.py +0 -0
  25. {constec-0.4.1 → constec-0.5.0}/constec/db/models/group.py +0 -0
  26. {constec-0.4.1 → constec-0.5.0}/constec/db/models/module.py +0 -0
  27. {constec-0.4.1 → constec-0.5.0}/constec/db/models/organization.py +0 -0
  28. {constec-0.4.1 → constec-0.5.0}/constec/db/models/person.py +0 -0
  29. {constec-0.4.1 → constec-0.5.0}/constec/db/models/session.py +0 -0
  30. {constec-0.4.1 → constec-0.5.0}/constec/db/models/tag.py +0 -0
  31. {constec-0.4.1 → constec-0.5.0}/constec/db/models/user.py +0 -0
  32. {constec-0.4.1 → constec-0.5.0}/constec/py.typed +0 -0
  33. {constec-0.4.1 → constec-0.5.0}/constec/services/__init__.py +0 -0
  34. {constec-0.4.1 → constec-0.5.0}/constec/services/encryption.py +0 -0
  35. {constec-0.4.1 → constec-0.5.0}/constec/shared/__init__.py +0 -0
  36. {constec-0.4.1 → constec-0.5.0}/constec/shared/exceptions.py +0 -0
  37. {constec-0.4.1 → constec-0.5.0}/constec/utils/__init__.py +0 -0
  38. {constec-0.4.1 → constec-0.5.0}/constec/utils/cuit.py +0 -0
  39. {constec-0.4.1 → constec-0.5.0}/constec/utils/password.py +0 -0
  40. {constec-0.4.1 → constec-0.5.0}/constec.egg-info/dependency_links.txt +0 -0
  41. {constec-0.4.1 → constec-0.5.0}/constec.egg-info/requires.txt +0 -0
  42. {constec-0.4.1 → constec-0.5.0}/constec.egg-info/top_level.txt +0 -0
  43. {constec-0.4.1 → constec-0.5.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: constec
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Base library for the Constec ecosystem - shared utilities, models, and namespace foundation
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/TpmyCT/constec-python
@@ -0,0 +1,270 @@
1
+ # Generated manually on 2026-02-03 for automations redesign (Fase 1)
2
+
3
+ import uuid
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+ import django.db.models.deletion
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+
11
+ dependencies = [
12
+ ('constec_db', '0005_event'),
13
+ ]
14
+
15
+ operations = [
16
+ # Create Automation model
17
+ migrations.CreateModel(
18
+ name='Automation',
19
+ fields=[
20
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
21
+ ('created_at', models.DateTimeField(auto_now_add=True)),
22
+ ('updated_at', models.DateTimeField(auto_now=True)),
23
+ ('name', models.CharField(max_length=255)),
24
+ ('description', models.TextField(blank=True)),
25
+ ('status', models.CharField(
26
+ choices=[('active', 'Active'), ('paused', 'Paused'), ('archived', 'Archived'), ('draft', 'Draft')],
27
+ default='draft',
28
+ max_length=20
29
+ )),
30
+ ('execution_mode', models.CharField(
31
+ choices=[('sequential', 'Sequential'), ('parallel', 'Parallel')],
32
+ default='sequential',
33
+ max_length=20
34
+ )),
35
+ ('max_executions', models.IntegerField(
36
+ blank=True,
37
+ help_text='Máximo número de ejecuciones (null = ilimitado)',
38
+ null=True
39
+ )),
40
+ ('execution_count', models.IntegerField(default=0)),
41
+ ('on_error', models.CharField(
42
+ choices=[('stop', 'Stop'), ('continue', 'Continue'), ('retry', 'Retry')],
43
+ default='stop',
44
+ max_length=20
45
+ )),
46
+ ('retry_count', models.IntegerField(default=3)),
47
+ ('retry_delay_seconds', models.IntegerField(default=60)),
48
+ ('last_executed_at', models.DateTimeField(blank=True, null=True)),
49
+ ('next_execution_at', models.DateTimeField(blank=True, db_index=True, null=True)),
50
+ ('tags', models.JSONField(blank=True, default=list)),
51
+ ('metadata', models.JSONField(blank=True, default=dict)),
52
+ ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='constec_db.company')),
53
+ ('created_by', models.ForeignKey(
54
+ on_delete=django.db.models.deletion.CASCADE,
55
+ related_name='created_automations',
56
+ to=settings.AUTH_USER_MODEL
57
+ )),
58
+ ],
59
+ options={
60
+ 'db_table': '"automations"."automations"',
61
+ 'ordering': ['-created_at'],
62
+ 'indexes': [
63
+ models.Index(fields=['company', 'status'], name='automations_auto_company_status_idx'),
64
+ models.Index(fields=['status', 'next_execution_at'], name='automations_auto_status_next_idx'),
65
+ ],
66
+ },
67
+ bases=(models.Model,),
68
+ ),
69
+
70
+ # Create Trigger model
71
+ migrations.CreateModel(
72
+ name='Trigger',
73
+ fields=[
74
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
75
+ ('created_at', models.DateTimeField(auto_now_add=True)),
76
+ ('updated_at', models.DateTimeField(auto_now=True)),
77
+ ('trigger_type', models.CharField(
78
+ choices=[('schedule', 'Schedule'), ('webhook', 'Webhook'), ('manual', 'Manual'), ('event', 'Event')],
79
+ max_length=20
80
+ )),
81
+ ('name', models.CharField(max_length=255)),
82
+ ('description', models.TextField(blank=True)),
83
+ ('is_enabled', models.BooleanField(default=True)),
84
+ ('config', models.JSONField(
85
+ default=dict,
86
+ help_text='Configuración específica del tipo de trigger (schedule, webhook, etc.)'
87
+ )),
88
+ ('last_triggered_at', models.DateTimeField(blank=True, null=True)),
89
+ ('trigger_count', models.IntegerField(default=0)),
90
+ ('priority', models.IntegerField(default=50, help_text='0-100, mayor = más prioridad')),
91
+ ('automation', models.ForeignKey(
92
+ on_delete=django.db.models.deletion.CASCADE,
93
+ related_name='triggers',
94
+ to='constec_db.automation'
95
+ )),
96
+ ],
97
+ options={
98
+ 'db_table': '"automations"."triggers"',
99
+ 'ordering': ['-priority', 'created_at'],
100
+ 'indexes': [
101
+ models.Index(fields=['automation', 'is_enabled'], name='automations_trig_automation_enabled_idx'),
102
+ models.Index(fields=['trigger_type', 'is_enabled'], name='automations_trig_type_enabled_idx'),
103
+ ],
104
+ },
105
+ bases=(models.Model,),
106
+ ),
107
+
108
+ # Create Action model
109
+ migrations.CreateModel(
110
+ name='Action',
111
+ fields=[
112
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
113
+ ('created_at', models.DateTimeField(auto_now_add=True)),
114
+ ('updated_at', models.DateTimeField(auto_now=True)),
115
+ ('action_type', models.CharField(
116
+ choices=[('notification', 'Notification'), ('webhook', 'Webhook'), ('batch_task', 'Batch Task'), ('flow_execution', 'Flow Execution')],
117
+ max_length=20
118
+ )),
119
+ ('name', models.CharField(max_length=255)),
120
+ ('description', models.TextField(blank=True)),
121
+ ('is_enabled', models.BooleanField(default=True)),
122
+ ('order', models.IntegerField(default=0, help_text='Orden de ejecución')),
123
+ ('config', models.JSONField(
124
+ default=dict,
125
+ help_text='Configuración específica del tipo de acción'
126
+ )),
127
+ ('on_error', models.CharField(
128
+ blank=True,
129
+ choices=[('stop', 'Stop'), ('continue', 'Continue'), ('retry', 'Retry')],
130
+ help_text='Override del on_error de la automation',
131
+ max_length=20,
132
+ null=True
133
+ )),
134
+ ('retry_count', models.IntegerField(
135
+ blank=True,
136
+ help_text='Override del retry_count de la automation',
137
+ null=True
138
+ )),
139
+ ('condition', models.JSONField(
140
+ blank=True,
141
+ help_text='Condición JSONLogic para ejecutar esta acción',
142
+ null=True
143
+ )),
144
+ ('last_executed_at', models.DateTimeField(blank=True, null=True)),
145
+ ('execution_count', models.IntegerField(default=0)),
146
+ ('success_count', models.IntegerField(default=0)),
147
+ ('failure_count', models.IntegerField(default=0)),
148
+ ('automation', models.ForeignKey(
149
+ on_delete=django.db.models.deletion.CASCADE,
150
+ related_name='actions',
151
+ to='constec_db.automation'
152
+ )),
153
+ ],
154
+ options={
155
+ 'db_table': '"automations"."actions"',
156
+ 'ordering': ['order', 'created_at'],
157
+ 'indexes': [
158
+ models.Index(fields=['automation', 'order'], name='automations_act_automation_order_idx'),
159
+ models.Index(fields=['action_type', 'is_enabled'], name='automations_act_type_enabled_idx'),
160
+ ],
161
+ },
162
+ bases=(models.Model,),
163
+ ),
164
+
165
+ # Create ExecutionLog model
166
+ migrations.CreateModel(
167
+ name='ExecutionLog',
168
+ fields=[
169
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
170
+ ('created_at', models.DateTimeField(auto_now_add=True)),
171
+ ('updated_at', models.DateTimeField(auto_now=True)),
172
+ ('status', models.CharField(
173
+ choices=[('pending', 'Pending'), ('running', 'Running'), ('completed', 'Completed'), ('failed', 'Failed'), ('partial', 'Partial')],
174
+ default='pending',
175
+ max_length=20
176
+ )),
177
+ ('triggered_by', models.CharField(
178
+ choices=[('schedule', 'Schedule'), ('webhook', 'Webhook'), ('manual', 'Manual'), ('system', 'System')],
179
+ max_length=20
180
+ )),
181
+ ('started_at', models.DateTimeField(auto_now_add=True)),
182
+ ('completed_at', models.DateTimeField(blank=True, null=True)),
183
+ ('duration_seconds', models.IntegerField(blank=True, null=True)),
184
+ ('execution_context', models.JSONField(
185
+ default=dict,
186
+ help_text='Variables y contexto disponible durante la ejecución'
187
+ )),
188
+ ('actions_total', models.IntegerField(default=0)),
189
+ ('actions_succeeded', models.IntegerField(default=0)),
190
+ ('actions_failed', models.IntegerField(default=0)),
191
+ ('actions_skipped', models.IntegerField(default=0)),
192
+ ('action_logs', models.JSONField(
193
+ default=list,
194
+ help_text='Array de logs detallados de cada acción ejecutada'
195
+ )),
196
+ ('error_message', models.TextField(blank=True)),
197
+ ('error_traceback', models.TextField(blank=True)),
198
+ ('automation', models.ForeignKey(
199
+ on_delete=django.db.models.deletion.CASCADE,
200
+ related_name='execution_logs',
201
+ to='constec_db.automation'
202
+ )),
203
+ ('trigger', models.ForeignKey(
204
+ blank=True,
205
+ null=True,
206
+ on_delete=django.db.models.deletion.SET_NULL,
207
+ related_name='execution_logs',
208
+ to='constec_db.trigger'
209
+ )),
210
+ ('triggered_by_user', models.ForeignKey(
211
+ blank=True,
212
+ null=True,
213
+ on_delete=django.db.models.deletion.SET_NULL,
214
+ related_name='triggered_executions',
215
+ to=settings.AUTH_USER_MODEL
216
+ )),
217
+ ],
218
+ options={
219
+ 'db_table': '"automations"."execution_logs"',
220
+ 'ordering': ['-started_at'],
221
+ 'indexes': [
222
+ models.Index(fields=['automation', 'started_at'], name='automations_exec_automation_started_idx'),
223
+ models.Index(fields=['status', 'started_at'], name='automations_exec_status_started_idx'),
224
+ models.Index(fields=['triggered_by', 'started_at'], name='automations_exec_triggered_started_idx'),
225
+ ],
226
+ },
227
+ bases=(models.Model,),
228
+ ),
229
+
230
+ # Create NotificationTemplate model
231
+ migrations.CreateModel(
232
+ name='NotificationTemplate',
233
+ fields=[
234
+ ('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
235
+ ('created_at', models.DateTimeField(auto_now_add=True)),
236
+ ('updated_at', models.DateTimeField(auto_now=True)),
237
+ ('name', models.CharField(max_length=255)),
238
+ ('description', models.TextField(blank=True)),
239
+ ('channel', models.CharField(
240
+ choices=[('email', 'Email'), ('whatsapp', 'WhatsApp'), ('sms', 'SMS'), ('push', 'Push Notification')],
241
+ max_length=20
242
+ )),
243
+ ('subject', models.CharField(
244
+ blank=True,
245
+ help_text='Asunto (solo para email)',
246
+ max_length=255
247
+ )),
248
+ ('body', models.TextField(help_text='Cuerpo del mensaje con variables {{variable}}')),
249
+ ('variables', models.JSONField(
250
+ default=list,
251
+ help_text="Array de nombres de variables esperadas ['customer_name', 'amount']"
252
+ )),
253
+ ('is_active', models.BooleanField(default=True)),
254
+ ('company', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='constec_db.company')),
255
+ ('created_by', models.ForeignKey(
256
+ on_delete=django.db.models.deletion.CASCADE,
257
+ related_name='created_templates',
258
+ to=settings.AUTH_USER_MODEL
259
+ )),
260
+ ],
261
+ options={
262
+ 'db_table': '"automations"."notification_templates"',
263
+ 'ordering': ['name'],
264
+ 'indexes': [
265
+ models.Index(fields=['company', 'channel', 'is_active'], name='automations_tmpl_company_channel_idx'),
266
+ ],
267
+ },
268
+ bases=(models.Model,),
269
+ ),
270
+ ]
@@ -42,7 +42,7 @@ from .flow import FlowTemplate, Flow
42
42
  from .session import Session, Message
43
43
 
44
44
  # Automations models (automations schema)
45
- from .automation import Event
45
+ from .automation import Event, Automation, Trigger, Action, ExecutionLog, NotificationTemplate
46
46
 
47
47
 
48
48
  __all__ = [
@@ -94,4 +94,9 @@ __all__ = [
94
94
  'Message',
95
95
  # Automations (automations schema)
96
96
  'Event',
97
+ 'Automation',
98
+ 'Trigger',
99
+ 'Action',
100
+ 'ExecutionLog',
101
+ 'NotificationTemplate',
97
102
  ]
@@ -0,0 +1,467 @@
1
+ """Automation models for event scheduling and task automation."""
2
+
3
+ import uuid
4
+ from django.db import models
5
+ from django.core.exceptions import ValidationError
6
+ from .base import UUIDModel
7
+ from .company import Company
8
+ from .user import User
9
+
10
+
11
+ class Automation(UUIDModel):
12
+ """
13
+ Automatización principal basada en el patrón Trigger-Action.
14
+
15
+ Una automatización define un conjunto de acciones que se ejecutan cuando
16
+ se activan uno o más triggers (disparadores).
17
+ """
18
+
19
+ STATUS_CHOICES = [
20
+ ('active', 'Active'),
21
+ ('paused', 'Paused'),
22
+ ('archived', 'Archived'),
23
+ ('draft', 'Draft'),
24
+ ]
25
+
26
+ EXECUTION_MODE_CHOICES = [
27
+ ('sequential', 'Sequential'),
28
+ ('parallel', 'Parallel'), # Futuro
29
+ ]
30
+
31
+ ON_ERROR_CHOICES = [
32
+ ('stop', 'Stop'),
33
+ ('continue', 'Continue'),
34
+ ('retry', 'Retry'),
35
+ ]
36
+
37
+ # FKs
38
+ company = models.ForeignKey(Company, on_delete=models.CASCADE, db_index=True)
39
+ created_by = models.ForeignKey(
40
+ User,
41
+ on_delete=models.CASCADE,
42
+ related_name='created_automations'
43
+ )
44
+
45
+ # Info básica
46
+ name = models.CharField(max_length=255)
47
+ description = models.TextField(blank=True)
48
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
49
+
50
+ # Configuración de ejecución
51
+ execution_mode = models.CharField(
52
+ max_length=20,
53
+ choices=EXECUTION_MODE_CHOICES,
54
+ default='sequential'
55
+ )
56
+ max_executions = models.IntegerField(
57
+ null=True,
58
+ blank=True,
59
+ help_text="Máximo número de ejecuciones (null = ilimitado)"
60
+ )
61
+ execution_count = models.IntegerField(default=0)
62
+
63
+ # Manejo de errores
64
+ on_error = models.CharField(
65
+ max_length=20,
66
+ choices=ON_ERROR_CHOICES,
67
+ default='stop'
68
+ )
69
+ retry_count = models.IntegerField(default=3)
70
+ retry_delay_seconds = models.IntegerField(default=60)
71
+
72
+ # Tracking de ejecución
73
+ last_executed_at = models.DateTimeField(null=True, blank=True)
74
+ next_execution_at = models.DateTimeField(null=True, blank=True, db_index=True)
75
+
76
+ # Metadata
77
+ tags = models.JSONField(default=list, blank=True)
78
+ metadata = models.JSONField(default=dict, blank=True)
79
+
80
+ class Meta:
81
+ db_table = '"automations"."automations"'
82
+ app_label = 'constec_db'
83
+ indexes = [
84
+ models.Index(fields=['company', 'status']),
85
+ models.Index(fields=['status', 'next_execution_at']),
86
+ ]
87
+ ordering = ['-created_at']
88
+
89
+ def __str__(self):
90
+ return f"{self.name} ({self.status})"
91
+
92
+
93
+ class Trigger(UUIDModel):
94
+ """
95
+ Trigger (disparador) que inicia la ejecución de una automatización.
96
+
97
+ Tipos soportados:
98
+ - schedule: Programado por fecha/hora
99
+ - webhook: Disparado por HTTP POST
100
+ - manual: Ejecución manual del usuario
101
+ - event: Disparado por evento del sistema (futuro)
102
+ """
103
+
104
+ TRIGGER_TYPE_CHOICES = [
105
+ ('schedule', 'Schedule'),
106
+ ('webhook', 'Webhook'),
107
+ ('manual', 'Manual'),
108
+ ('event', 'Event'),
109
+ ]
110
+
111
+ # FKs
112
+ automation = models.ForeignKey(
113
+ Automation,
114
+ on_delete=models.CASCADE,
115
+ related_name='triggers'
116
+ )
117
+
118
+ # Info básica
119
+ trigger_type = models.CharField(max_length=20, choices=TRIGGER_TYPE_CHOICES)
120
+ name = models.CharField(max_length=255)
121
+ description = models.TextField(blank=True)
122
+ is_enabled = models.BooleanField(default=True)
123
+
124
+ # Configuración tipo-específica
125
+ config = models.JSONField(
126
+ default=dict,
127
+ help_text="Configuración específica del tipo de trigger (schedule, webhook, etc.)"
128
+ )
129
+
130
+ # Tracking
131
+ last_triggered_at = models.DateTimeField(null=True, blank=True)
132
+ trigger_count = models.IntegerField(default=0)
133
+ priority = models.IntegerField(default=50, help_text="0-100, mayor = más prioridad")
134
+
135
+ class Meta:
136
+ db_table = '"automations"."triggers"'
137
+ app_label = 'constec_db'
138
+ indexes = [
139
+ models.Index(fields=['automation', 'is_enabled']),
140
+ models.Index(fields=['trigger_type', 'is_enabled']),
141
+ ]
142
+ ordering = ['-priority', 'created_at']
143
+
144
+ def __str__(self):
145
+ return f"{self.trigger_type}: {self.name}"
146
+
147
+
148
+ class Action(UUIDModel):
149
+ """
150
+ Acción que se ejecuta como parte de una automatización.
151
+
152
+ Tipos soportados:
153
+ - notification: Enviar notificación (email, SMS, WhatsApp, push)
154
+ - webhook: Ejecutar HTTP request
155
+ - batch_task: Ejecutar tarea batch (futuro)
156
+ - flow_execution: Ejecutar workflow LangGraph (futuro)
157
+ """
158
+
159
+ ACTION_TYPE_CHOICES = [
160
+ ('notification', 'Notification'),
161
+ ('webhook', 'Webhook'),
162
+ ('batch_task', 'Batch Task'),
163
+ ('flow_execution', 'Flow Execution'),
164
+ ]
165
+
166
+ ON_ERROR_CHOICES = [
167
+ ('stop', 'Stop'),
168
+ ('continue', 'Continue'),
169
+ ('retry', 'Retry'),
170
+ ]
171
+
172
+ # FKs
173
+ automation = models.ForeignKey(
174
+ Automation,
175
+ on_delete=models.CASCADE,
176
+ related_name='actions'
177
+ )
178
+
179
+ # Info básica
180
+ action_type = models.CharField(max_length=20, choices=ACTION_TYPE_CHOICES)
181
+ name = models.CharField(max_length=255)
182
+ description = models.TextField(blank=True)
183
+ is_enabled = models.BooleanField(default=True)
184
+ order = models.IntegerField(default=0, help_text="Orden de ejecución")
185
+
186
+ # Configuración tipo-específica
187
+ config = models.JSONField(
188
+ default=dict,
189
+ help_text="Configuración específica del tipo de acción"
190
+ )
191
+
192
+ # Manejo de errores (override de automation)
193
+ on_error = models.CharField(
194
+ max_length=20,
195
+ choices=ON_ERROR_CHOICES,
196
+ null=True,
197
+ blank=True,
198
+ help_text="Override del on_error de la automation"
199
+ )
200
+ retry_count = models.IntegerField(
201
+ null=True,
202
+ blank=True,
203
+ help_text="Override del retry_count de la automation"
204
+ )
205
+
206
+ # Ejecución condicional (futuro)
207
+ condition = models.JSONField(
208
+ null=True,
209
+ blank=True,
210
+ help_text="Condición JSONLogic para ejecutar esta acción"
211
+ )
212
+
213
+ # Tracking
214
+ last_executed_at = models.DateTimeField(null=True, blank=True)
215
+ execution_count = models.IntegerField(default=0)
216
+ success_count = models.IntegerField(default=0)
217
+ failure_count = models.IntegerField(default=0)
218
+
219
+ class Meta:
220
+ db_table = '"automations"."actions"'
221
+ app_label = 'constec_db'
222
+ indexes = [
223
+ models.Index(fields=['automation', 'order']),
224
+ models.Index(fields=['action_type', 'is_enabled']),
225
+ ]
226
+ ordering = ['order', 'created_at']
227
+
228
+ def __str__(self):
229
+ return f"{self.action_type}: {self.name}"
230
+
231
+
232
+ class ExecutionLog(UUIDModel):
233
+ """
234
+ Log de ejecución de una automatización.
235
+
236
+ Registra cada ejecución con su estado, duración, errores y detalles
237
+ de cada acción ejecutada.
238
+ """
239
+
240
+ STATUS_CHOICES = [
241
+ ('pending', 'Pending'),
242
+ ('running', 'Running'),
243
+ ('completed', 'Completed'),
244
+ ('failed', 'Failed'),
245
+ ('partial', 'Partial'), # Algunas acciones fallaron
246
+ ]
247
+
248
+ TRIGGERED_BY_CHOICES = [
249
+ ('schedule', 'Schedule'),
250
+ ('webhook', 'Webhook'),
251
+ ('manual', 'Manual'),
252
+ ('system', 'System'),
253
+ ]
254
+
255
+ # FKs
256
+ automation = models.ForeignKey(
257
+ Automation,
258
+ on_delete=models.CASCADE,
259
+ related_name='execution_logs'
260
+ )
261
+ trigger = models.ForeignKey(
262
+ Trigger,
263
+ on_delete=models.SET_NULL,
264
+ null=True,
265
+ blank=True,
266
+ related_name='execution_logs'
267
+ )
268
+ triggered_by_user = models.ForeignKey(
269
+ User,
270
+ on_delete=models.SET_NULL,
271
+ null=True,
272
+ blank=True,
273
+ related_name='triggered_executions'
274
+ )
275
+
276
+ # Estado
277
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='pending')
278
+ triggered_by = models.CharField(max_length=20, choices=TRIGGERED_BY_CHOICES)
279
+
280
+ # Timing
281
+ started_at = models.DateTimeField(auto_now_add=True)
282
+ completed_at = models.DateTimeField(null=True, blank=True)
283
+ duration_seconds = models.IntegerField(null=True, blank=True)
284
+
285
+ # Contexto de ejecución
286
+ execution_context = models.JSONField(
287
+ default=dict,
288
+ help_text="Variables y contexto disponible durante la ejecución"
289
+ )
290
+
291
+ # Estadísticas de acciones
292
+ actions_total = models.IntegerField(default=0)
293
+ actions_succeeded = models.IntegerField(default=0)
294
+ actions_failed = models.IntegerField(default=0)
295
+ actions_skipped = models.IntegerField(default=0)
296
+
297
+ # Log detallado de cada acción
298
+ action_logs = models.JSONField(
299
+ default=list,
300
+ help_text="Array de logs detallados de cada acción ejecutada"
301
+ )
302
+
303
+ # Errores
304
+ error_message = models.TextField(blank=True)
305
+ error_traceback = models.TextField(blank=True)
306
+
307
+ class Meta:
308
+ db_table = '"automations"."execution_logs"'
309
+ app_label = 'constec_db'
310
+ indexes = [
311
+ models.Index(fields=['automation', 'started_at']),
312
+ models.Index(fields=['status', 'started_at']),
313
+ models.Index(fields=['triggered_by', 'started_at']),
314
+ ]
315
+ ordering = ['-started_at']
316
+
317
+ def __str__(self):
318
+ return f"{self.automation.name} - {self.status} ({self.started_at})"
319
+
320
+
321
+ class NotificationTemplate(UUIDModel):
322
+ """
323
+ Template para notificaciones reutilizables.
324
+
325
+ Soporta variables en formato {{variable_name}} que se reemplazan
326
+ en tiempo de ejecución.
327
+ """
328
+
329
+ CHANNEL_CHOICES = [
330
+ ('email', 'Email'),
331
+ ('whatsapp', 'WhatsApp'),
332
+ ('sms', 'SMS'),
333
+ ('push', 'Push Notification'),
334
+ ]
335
+
336
+ # FKs
337
+ company = models.ForeignKey(Company, on_delete=models.CASCADE, db_index=True)
338
+ created_by = models.ForeignKey(
339
+ User,
340
+ on_delete=models.CASCADE,
341
+ related_name='created_templates'
342
+ )
343
+
344
+ # Info básica
345
+ name = models.CharField(max_length=255)
346
+ description = models.TextField(blank=True)
347
+ channel = models.CharField(max_length=20, choices=CHANNEL_CHOICES)
348
+
349
+ # Contenido
350
+ subject = models.CharField(
351
+ max_length=255,
352
+ blank=True,
353
+ help_text="Asunto (solo para email)"
354
+ )
355
+ body = models.TextField(help_text="Cuerpo del mensaje con variables {{variable}}")
356
+
357
+ # Variables esperadas
358
+ variables = models.JSONField(
359
+ default=list,
360
+ help_text="Array de nombres de variables esperadas ['customer_name', 'amount']"
361
+ )
362
+
363
+ # Estado
364
+ is_active = models.BooleanField(default=True)
365
+
366
+ class Meta:
367
+ db_table = '"automations"."notification_templates"'
368
+ app_label = 'constec_db'
369
+ indexes = [
370
+ models.Index(fields=['company', 'channel', 'is_active']),
371
+ ]
372
+ ordering = ['name']
373
+
374
+ def __str__(self):
375
+ return f"{self.channel}: {self.name}"
376
+
377
+
378
+ class Event(UUIDModel):
379
+ """
380
+ Evento programado con soporte para múltiples tipos y recurrencia flexible.
381
+
382
+ Tipos soportados:
383
+ - notification: Notificaciones programadas
384
+ - automation: Automatizaciones programadas (acciones HTTP, workflows, etc.)
385
+ """
386
+
387
+ EVENT_TYPE_CHOICES = [
388
+ ('notification', 'Notification'),
389
+ ('automation', 'Automation'),
390
+ ]
391
+
392
+ STATUS_CHOICES = [
393
+ ('active', 'Active'), # Activo y programado
394
+ ('paused', 'Paused'), # Pausado temporalmente
395
+ ('completed', 'Completed'), # Completado (para puntuales)
396
+ ('failed', 'Failed'), # Falló la última ejecución
397
+ ('cancelled', 'Cancelled'), # Cancelado permanentemente
398
+ ]
399
+
400
+ RECURRENCE_TYPE_CHOICES = [
401
+ ('punctual', 'Punctual'), # Una o más veces en fechas específicas
402
+ ('periodic', 'Periodic'), # Recurrente
403
+ ]
404
+
405
+ # FKs
406
+ company = models.ForeignKey(Company, on_delete=models.CASCADE, db_index=True)
407
+
408
+ # Tipo de evento
409
+ event_type = models.CharField(max_length=50, choices=EVENT_TYPE_CHOICES)
410
+
411
+ # Info básica
412
+ title = models.CharField(max_length=255)
413
+ description = models.TextField(blank=True)
414
+ status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
415
+
416
+ # Recurrencia
417
+ recurrence_type = models.CharField(
418
+ max_length=20,
419
+ choices=RECURRENCE_TYPE_CHOICES,
420
+ help_text="Tipo de recurrencia: puntual (fechas específicas) o periódico"
421
+ )
422
+ recurrence_config = models.JSONField(
423
+ help_text="Configuración de recurrencia en formato JSON"
424
+ )
425
+
426
+ # Configuración específica del tipo de evento
427
+ config = models.JSONField(
428
+ default=dict,
429
+ help_text="Configuración específica según event_type (mensaje, URL, etc.)"
430
+ )
431
+
432
+ # Control de ejecución
433
+ next_execution_at = models.DateTimeField(
434
+ null=True,
435
+ blank=True,
436
+ db_index=True,
437
+ help_text="Próxima fecha/hora de ejecución calculada"
438
+ )
439
+ last_executed_at = models.DateTimeField(
440
+ null=True,
441
+ blank=True,
442
+ help_text="Última vez que se ejecutó"
443
+ )
444
+ execution_count = models.IntegerField(
445
+ default=0,
446
+ help_text="Cantidad de veces que se ha ejecutado"
447
+ )
448
+
449
+ # Auditoría
450
+ created_by = models.ForeignKey(
451
+ User,
452
+ on_delete=models.CASCADE,
453
+ related_name='created_events'
454
+ )
455
+
456
+ class Meta:
457
+ db_table = '"automations"."events"'
458
+ app_label = 'constec_db'
459
+ indexes = [
460
+ models.Index(fields=['company', 'event_type']),
461
+ models.Index(fields=['status', 'next_execution_at']),
462
+ models.Index(fields=['recurrence_type']),
463
+ ]
464
+ ordering = ['next_execution_at']
465
+
466
+ def __str__(self):
467
+ return f"{self.event_type}: {self.title}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: constec
3
- Version: 0.4.1
3
+ Version: 0.5.0
4
4
  Summary: Base library for the Constec ecosystem - shared utilities, models, and namespace foundation
5
5
  License: MIT
6
6
  Project-URL: Homepage, https://github.com/TpmyCT/constec-python
@@ -14,6 +14,7 @@ constec/db/migrations/0002_module_level.py
14
14
  constec/db/migrations/0003_remove_module_level.py
15
15
  constec/db/migrations/0004_rename_entities_company_cuit_idx_entities_company_e2c50f_idx_and_more.py
16
16
  constec/db/migrations/0005_event.py
17
+ constec/db/migrations/0006_automation_trigger_action_executionlog_notificationtemplate.py
17
18
  constec/db/migrations/__init__.py
18
19
  constec/db/models/__init__.py
19
20
  constec/db/models/automation.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "constec"
7
- version = "0.4.1"
7
+ version = "0.5.0"
8
8
  description = "Base library for the Constec ecosystem - shared utilities, models, and namespace foundation"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -1,99 +0,0 @@
1
- """Automation models for event scheduling and task automation."""
2
-
3
- import uuid
4
- from django.db import models
5
- from .base import UUIDModel
6
- from .company import Company
7
- from .user import User
8
-
9
-
10
- class Event(UUIDModel):
11
- """
12
- Evento programado con soporte para múltiples tipos y recurrencia flexible.
13
-
14
- Tipos soportados:
15
- - notification: Notificaciones programadas
16
- - automation: Automatizaciones programadas (acciones HTTP, workflows, etc.)
17
- """
18
-
19
- EVENT_TYPE_CHOICES = [
20
- ('notification', 'Notification'),
21
- ('automation', 'Automation'),
22
- ]
23
-
24
- STATUS_CHOICES = [
25
- ('active', 'Active'), # Activo y programado
26
- ('paused', 'Paused'), # Pausado temporalmente
27
- ('completed', 'Completed'), # Completado (para puntuales)
28
- ('failed', 'Failed'), # Falló la última ejecución
29
- ('cancelled', 'Cancelled'), # Cancelado permanentemente
30
- ]
31
-
32
- RECURRENCE_TYPE_CHOICES = [
33
- ('punctual', 'Punctual'), # Una o más veces en fechas específicas
34
- ('periodic', 'Periodic'), # Recurrente
35
- ]
36
-
37
- # FKs
38
- company = models.ForeignKey(Company, on_delete=models.CASCADE, db_index=True)
39
-
40
- # Tipo de evento
41
- event_type = models.CharField(max_length=50, choices=EVENT_TYPE_CHOICES)
42
-
43
- # Info básica
44
- title = models.CharField(max_length=255)
45
- description = models.TextField(blank=True)
46
- status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='active')
47
-
48
- # Recurrencia
49
- recurrence_type = models.CharField(
50
- max_length=20,
51
- choices=RECURRENCE_TYPE_CHOICES,
52
- help_text="Tipo de recurrencia: puntual (fechas específicas) o periódico"
53
- )
54
- recurrence_config = models.JSONField(
55
- help_text="Configuración de recurrencia en formato JSON"
56
- )
57
-
58
- # Configuración específica del tipo de evento
59
- config = models.JSONField(
60
- default=dict,
61
- help_text="Configuración específica según event_type (mensaje, URL, etc.)"
62
- )
63
-
64
- # Control de ejecución
65
- next_execution_at = models.DateTimeField(
66
- null=True,
67
- blank=True,
68
- db_index=True,
69
- help_text="Próxima fecha/hora de ejecución calculada"
70
- )
71
- last_executed_at = models.DateTimeField(
72
- null=True,
73
- blank=True,
74
- help_text="Última vez que se ejecutó"
75
- )
76
- execution_count = models.IntegerField(
77
- default=0,
78
- help_text="Cantidad de veces que se ha ejecutado"
79
- )
80
-
81
- # Auditoría
82
- created_by = models.ForeignKey(
83
- User,
84
- on_delete=models.CASCADE,
85
- related_name='created_events'
86
- )
87
-
88
- class Meta:
89
- db_table = '"automations"."events"'
90
- app_label = 'constec_db'
91
- indexes = [
92
- models.Index(fields=['company', 'event_type']),
93
- models.Index(fields=['status', 'next_execution_at']),
94
- models.Index(fields=['recurrence_type']),
95
- ]
96
- ordering = ['next_execution_at']
97
-
98
- def __str__(self):
99
- return f"{self.event_type}: {self.title}"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes