@nextsparkjs/theme-crm 0.1.0-beta.1

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 (140) hide show
  1. package/CRM_PLAN.md +343 -0
  2. package/about.md +122 -0
  3. package/config/app.config.ts +185 -0
  4. package/config/billing.config.ts +187 -0
  5. package/config/dashboard.config.ts +372 -0
  6. package/config/dev.config.ts +55 -0
  7. package/config/features.config.ts +336 -0
  8. package/config/flows.config.ts +511 -0
  9. package/config/permissions.config.ts +297 -0
  10. package/config/theme.config.ts +111 -0
  11. package/entities/activities/activities.config.ts +61 -0
  12. package/entities/activities/activities.fields.ts +362 -0
  13. package/entities/activities/activities.service.ts +503 -0
  14. package/entities/activities/activities.types.ts +117 -0
  15. package/entities/activities/messages/en.json +123 -0
  16. package/entities/activities/messages/es.json +123 -0
  17. package/entities/activities/migrations/020_activities_table.sql +123 -0
  18. package/entities/activities/migrations/021_activities_metas.sql +114 -0
  19. package/entities/activities/migrations/022_activities_sample_data.sql +420 -0
  20. package/entities/campaigns/campaigns.config.ts +61 -0
  21. package/entities/campaigns/campaigns.fields.ts +413 -0
  22. package/entities/campaigns/campaigns.service.ts +426 -0
  23. package/entities/campaigns/campaigns.types.ts +124 -0
  24. package/entities/campaigns/messages/en.json +145 -0
  25. package/entities/campaigns/messages/es.json +145 -0
  26. package/entities/campaigns/migrations/001_campaigns_table.sql +127 -0
  27. package/entities/campaigns/migrations/002_campaigns_metas.sql +114 -0
  28. package/entities/campaigns/migrations/003_campaigns_sample_data.sql +364 -0
  29. package/entities/companies/companies.config.ts +61 -0
  30. package/entities/companies/companies.fields.ts +429 -0
  31. package/entities/companies/companies.service.ts +566 -0
  32. package/entities/companies/companies.types.ts +125 -0
  33. package/entities/companies/messages/en.json +146 -0
  34. package/entities/companies/messages/es.json +146 -0
  35. package/entities/companies/migrations/001_companies_table.sql +150 -0
  36. package/entities/companies/migrations/002_companies_metas.sql +114 -0
  37. package/entities/companies/migrations/003_companies_sample_data.sql +246 -0
  38. package/entities/contacts/contacts.config.ts +61 -0
  39. package/entities/contacts/contacts.fields.ts +359 -0
  40. package/entities/contacts/contacts.service.ts +509 -0
  41. package/entities/contacts/contacts.types.ts +108 -0
  42. package/entities/contacts/messages/en.json +117 -0
  43. package/entities/contacts/messages/es.json +117 -0
  44. package/entities/contacts/migrations/001_contacts_table.sql +134 -0
  45. package/entities/contacts/migrations/002_contacts_metas.sql +114 -0
  46. package/entities/contacts/migrations/003_contacts_sample_data.sql +421 -0
  47. package/entities/leads/leads.config.ts +61 -0
  48. package/entities/leads/leads.fields.ts +336 -0
  49. package/entities/leads/leads.service.ts +496 -0
  50. package/entities/leads/leads.types.ts +114 -0
  51. package/entities/leads/messages/en.json +132 -0
  52. package/entities/leads/messages/es.json +132 -0
  53. package/entities/leads/migrations/001_leads_table.sql +150 -0
  54. package/entities/leads/migrations/002_leads_metas.sql +120 -0
  55. package/entities/leads/migrations/003_leads_sample_data.sql +242 -0
  56. package/entities/notes/messages/en.json +114 -0
  57. package/entities/notes/messages/es.json +114 -0
  58. package/entities/notes/migrations/020_notes_table.sql +118 -0
  59. package/entities/notes/migrations/021_notes_metas.sql +114 -0
  60. package/entities/notes/migrations/022_notes_sample_data.sql +275 -0
  61. package/entities/notes/notes.config.ts +61 -0
  62. package/entities/notes/notes.fields.ts +283 -0
  63. package/entities/notes/notes.service.ts +320 -0
  64. package/entities/notes/notes.types.ts +102 -0
  65. package/entities/opportunities/messages/en.json +107 -0
  66. package/entities/opportunities/messages/es.json +107 -0
  67. package/entities/opportunities/migrations/010_opportunities_table.sql +145 -0
  68. package/entities/opportunities/migrations/011_opportunities_metas.sql +114 -0
  69. package/entities/opportunities/migrations/012_opportunities_sample_data.sql +438 -0
  70. package/entities/opportunities/opportunities.config.ts +61 -0
  71. package/entities/opportunities/opportunities.fields.ts +416 -0
  72. package/entities/opportunities/opportunities.service.ts +525 -0
  73. package/entities/opportunities/opportunities.types.ts +135 -0
  74. package/entities/pipelines/messages/en.json +115 -0
  75. package/entities/pipelines/messages/es.json +115 -0
  76. package/entities/pipelines/migrations/001_pipelines_table.sql +106 -0
  77. package/entities/pipelines/migrations/002_pipelines_metas.sql +114 -0
  78. package/entities/pipelines/migrations/003_pipelines_sample_data.sql +91 -0
  79. package/entities/pipelines/pipelines.config.ts +62 -0
  80. package/entities/pipelines/pipelines.fields.ts +193 -0
  81. package/entities/pipelines/pipelines.service.ts +383 -0
  82. package/entities/pipelines/pipelines.types.ts +78 -0
  83. package/entities/products/messages/en.json +135 -0
  84. package/entities/products/messages/es.json +135 -0
  85. package/entities/products/migrations/001_products_table.sql +117 -0
  86. package/entities/products/migrations/002_products_metas.sql +114 -0
  87. package/entities/products/migrations/003_products_sample_data.sql +247 -0
  88. package/entities/products/products.config.ts +62 -0
  89. package/entities/products/products.fields.ts +361 -0
  90. package/entities/products/products.service.ts +437 -0
  91. package/entities/products/products.types.ts +125 -0
  92. package/lib/crm-constants.ts +77 -0
  93. package/lib/crm-utils.ts +185 -0
  94. package/lib/selectors.ts +333 -0
  95. package/messages/en.json +131 -0
  96. package/messages/es.json +131 -0
  97. package/migrations/999_theme_sample_data.sql +473 -0
  98. package/package.json +18 -0
  99. package/pendings.md +205 -0
  100. package/permissions-matrix.md +216 -0
  101. package/styles/components.css +414 -0
  102. package/styles/crm-theme.css +358 -0
  103. package/styles/globals.css +576 -0
  104. package/styles/variables.css +111 -0
  105. package/templates/dashboard/(main)/activities/components/ActivityCard.tsx +169 -0
  106. package/templates/dashboard/(main)/activities/components/ActivityTimeline.tsx +165 -0
  107. package/templates/dashboard/(main)/activities/page.tsx +297 -0
  108. package/templates/dashboard/(main)/campaigns/page.tsx +373 -0
  109. package/templates/dashboard/(main)/companies/page.tsx +296 -0
  110. package/templates/dashboard/(main)/contacts/page.tsx +347 -0
  111. package/templates/dashboard/(main)/layout.tsx +98 -0
  112. package/templates/dashboard/(main)/leads/page.tsx +335 -0
  113. package/templates/dashboard/(main)/opportunities/[id]/edit/page.tsx +95 -0
  114. package/templates/dashboard/(main)/opportunities/create/page.tsx +94 -0
  115. package/templates/dashboard/(main)/opportunities/page.tsx +350 -0
  116. package/templates/dashboard/(main)/pipelines/[id]/edit/page.tsx +95 -0
  117. package/templates/dashboard/(main)/pipelines/[id]/page.tsx +143 -0
  118. package/templates/dashboard/(main)/pipelines/create/page.tsx +94 -0
  119. package/templates/dashboard/(main)/pipelines/page.tsx +234 -0
  120. package/templates/dashboard/(main)/products/[id]/edit/page.tsx +97 -0
  121. package/templates/dashboard/(main)/products/[id]/page.tsx +509 -0
  122. package/templates/dashboard/(main)/products/create/page.tsx +96 -0
  123. package/templates/dashboard/(main)/products/page.tsx +308 -0
  124. package/templates/shared/ActionButtons.tsx +41 -0
  125. package/templates/shared/CRMDashboard.tsx +519 -0
  126. package/templates/shared/CRMDataTable.tsx +441 -0
  127. package/templates/shared/CRMMetricCard.tsx +76 -0
  128. package/templates/shared/CRMMobileNav.tsx +172 -0
  129. package/templates/shared/CRMSidebar.tsx +346 -0
  130. package/templates/shared/CRMTopBar.tsx +265 -0
  131. package/templates/shared/DealCard.tsx +123 -0
  132. package/templates/shared/EntityCard.tsx +58 -0
  133. package/templates/shared/OpportunityForm.tsx +649 -0
  134. package/templates/shared/PipelineForm.tsx +367 -0
  135. package/templates/shared/PipelineKanban.tsx +194 -0
  136. package/templates/shared/QuickFilters.tsx +47 -0
  137. package/templates/shared/StageColumn.tsx +175 -0
  138. package/templates/shared/StageSelect.tsx +177 -0
  139. package/templates/shared/StagesRepeater.tsx +317 -0
  140. package/templates/shared/index.ts +9 -0
@@ -0,0 +1,242 @@
1
+ -- ============================================================================
2
+ -- Sample data for leads table - CRM Theme
3
+ -- Aligned with schema in 001_leads_table.sql
4
+ -- Uses users from 999_theme_sample_data.sql
5
+ -- ============================================================================
6
+
7
+ -- Clean existing leads data
8
+ DELETE FROM "leads" WHERE "teamId" = 'team-crm-company';
9
+
10
+ INSERT INTO "leads" (
11
+ id,
12
+ "companyName",
13
+ "contactName",
14
+ email,
15
+ phone,
16
+ website,
17
+ source,
18
+ status,
19
+ score,
20
+ industry,
21
+ "companySize",
22
+ budget,
23
+ "assignedTo",
24
+ notes,
25
+ "userId",
26
+ "teamId",
27
+ "createdAt",
28
+ "updatedAt"
29
+ ) VALUES
30
+ -- Hot Leads (High Priority)
31
+ (
32
+ 'lead-crm-001',
33
+ 'NewTech Ventures',
34
+ 'Sarah Johnson',
35
+ 'sarah.johnson@newtech-ventures.com',
36
+ '+1-555-1001',
37
+ 'https://newtech-ventures.com',
38
+ 'web',
39
+ 'qualified',
40
+ 85,
41
+ 'Technology',
42
+ '51-200',
43
+ 50000.00,
44
+ 'usr-crm-sales-rep',
45
+ 'Very interested in our enterprise solution. Has budget approval.',
46
+ 'usr-crm-sales-mgr',
47
+ 'team-crm-company',
48
+ NOW() - INTERVAL '5 days',
49
+ NOW() - INTERVAL '2 days'
50
+ ),
51
+ (
52
+ 'lead-crm-002',
53
+ 'Innovate Corp',
54
+ 'Michael Rodriguez',
55
+ 'michael.rodriguez@innovate-corp.com',
56
+ '+1-555-1002',
57
+ 'https://innovate-corp.com',
58
+ 'referral',
59
+ 'qualified',
60
+ 92,
61
+ 'Manufacturing',
62
+ '201-500',
63
+ 120000.00,
64
+ 'usr-crm-sales-rep',
65
+ 'Referral from existing customer Global Manufacturing. Decision maker.',
66
+ 'usr-crm-ceo',
67
+ 'team-crm-company',
68
+ NOW() - INTERVAL '3 days',
69
+ NOW() - INTERVAL '1 day'
70
+ ),
71
+ (
72
+ 'lead-crm-003',
73
+ 'Future Health Systems',
74
+ 'Emily Chen',
75
+ 'emily.chen@future-health.com',
76
+ '+1-555-1003',
77
+ 'https://future-health.com',
78
+ 'social_media',
79
+ 'qualified',
80
+ 88,
81
+ 'Healthcare',
82
+ '201-500',
83
+ 85000.00,
84
+ 'usr-crm-sales-mgr',
85
+ 'LinkedIn connection. Expressed interest in healthcare-specific features.',
86
+ 'usr-crm-ceo',
87
+ 'team-crm-company',
88
+ NOW() - INTERVAL '7 days',
89
+ NOW() - INTERVAL '3 days'
90
+ ),
91
+
92
+ -- Warm Leads (Medium Priority)
93
+ (
94
+ 'lead-crm-004',
95
+ 'Growth Solutions LLC',
96
+ 'David Thompson',
97
+ 'david.thompson@growth-solutions.com',
98
+ '+1-555-1004',
99
+ 'https://growth-solutions.com',
100
+ 'cold_call',
101
+ 'contacted',
102
+ 72,
103
+ 'Consulting',
104
+ '11-50',
105
+ 25000.00,
106
+ 'usr-crm-sales-rep',
107
+ 'Initial call went well. Needs follow-up demo.',
108
+ 'usr-crm-sales-mgr',
109
+ 'team-crm-company',
110
+ NOW() - INTERVAL '10 days',
111
+ NOW() - INTERVAL '5 days'
112
+ ),
113
+ (
114
+ 'lead-crm-005',
115
+ 'SmartEdu Technologies',
116
+ 'Lisa Anderson',
117
+ 'lisa.anderson@smartedu.com',
118
+ '+1-555-1005',
119
+ 'https://smartedu.com',
120
+ 'advertising',
121
+ 'contacted',
122
+ 65,
123
+ 'Education Technology',
124
+ '51-200',
125
+ 32000.00,
126
+ 'usr-crm-sales-rep',
127
+ 'Came through Google Ads. Evaluating multiple vendors.',
128
+ 'usr-crm-sales-rep',
129
+ 'team-crm-company',
130
+ NOW() - INTERVAL '12 days',
131
+ NOW() - INTERVAL '7 days'
132
+ ),
133
+ (
134
+ 'lead-crm-006',
135
+ 'Digital Retail Group',
136
+ 'Robert Wilson',
137
+ 'robert.wilson@digital-retail.com',
138
+ '+1-555-1006',
139
+ 'https://digital-retail.com',
140
+ 'trade_show',
141
+ 'contacted',
142
+ 58,
143
+ 'Retail',
144
+ '500+',
145
+ 150000.00,
146
+ 'usr-crm-sales-mgr',
147
+ 'Met at CRM Conference 2024. Large potential deal.',
148
+ 'usr-crm-ceo',
149
+ 'team-crm-company',
150
+ NOW() - INTERVAL '15 days',
151
+ NOW() - INTERVAL '10 days'
152
+ ),
153
+
154
+ -- Cold Leads (Lower Priority)
155
+ (
156
+ 'lead-crm-007',
157
+ 'Local Services Co',
158
+ 'Jennifer Brown',
159
+ 'jennifer.brown@local-services.com',
160
+ '+1-555-1007',
161
+ 'https://local-services.com',
162
+ 'email',
163
+ 'new',
164
+ 35,
165
+ 'Professional Services',
166
+ '11-50',
167
+ 8000.00,
168
+ NULL,
169
+ 'Downloaded whitepaper. No response to follow-up emails yet.',
170
+ 'usr-crm-marketing',
171
+ 'team-crm-company',
172
+ NOW() - INTERVAL '20 days',
173
+ NOW() - INTERVAL '15 days'
174
+ ),
175
+ (
176
+ 'lead-crm-008',
177
+ 'Miller Family Business',
178
+ 'James Miller',
179
+ 'james.miller@family-business.com',
180
+ '+1-555-1008',
181
+ 'https://family-business.com',
182
+ 'cold_call',
183
+ 'contacted',
184
+ 42,
185
+ 'Family Business',
186
+ '1-10',
187
+ 12000.00,
188
+ 'usr-crm-sales-rep',
189
+ 'Small business owner. Budget constraints but interested.',
190
+ 'usr-crm-sales-rep',
191
+ 'team-crm-company',
192
+ NOW() - INTERVAL '18 days',
193
+ NOW() - INTERVAL '12 days'
194
+ ),
195
+
196
+ -- Converted Lead (Example of successful conversion)
197
+ (
198
+ 'lead-crm-009',
199
+ 'TechnoSoft Solutions',
200
+ 'Amanda Davis',
201
+ 'amanda.davis@technosoft.com',
202
+ '+1-555-1009',
203
+ 'https://technosoft.com',
204
+ 'referral',
205
+ 'converted',
206
+ 95,
207
+ 'Software Development',
208
+ '51-200',
209
+ 25000.00,
210
+ 'usr-crm-sales-mgr',
211
+ 'Successfully converted to customer. Now company_001.',
212
+ 'usr-crm-ceo',
213
+ 'team-crm-company',
214
+ NOW() - INTERVAL '60 days',
215
+ NOW() - INTERVAL '35 days'
216
+ ),
217
+
218
+ -- International Lead
219
+ (
220
+ 'lead-crm-010',
221
+ 'Innovacion Mexico',
222
+ 'Carlos Garcia',
223
+ 'carlos.garcia@innovacion-mx.com',
224
+ '+52-555-1010',
225
+ 'https://innovacion-mx.com',
226
+ 'partner',
227
+ 'qualified',
228
+ 78,
229
+ 'Technology',
230
+ '51-200',
231
+ 35000.00,
232
+ 'usr-crm-sales-mgr',
233
+ 'Partner referral from Mexico. Needs Spanish-speaking support.',
234
+ 'usr-crm-ceo',
235
+ 'team-crm-company',
236
+ NOW() - INTERVAL '8 days',
237
+ NOW() - INTERVAL '4 days'
238
+ );
239
+
240
+ -- Note: convertedToContactId and convertedToCompanyId are UUID columns
241
+ -- Cannot link to TEXT IDs like 'contact-crm-001' directly
242
+ -- The conversion tracking can be done at application level
@@ -0,0 +1,114 @@
1
+ {
2
+ "entity": {
3
+ "name": "Note",
4
+ "plural": "Notes",
5
+ "description": "Manage notes and documentation"
6
+ },
7
+ "fields": {
8
+ "entityType": {
9
+ "label": "Related Entity Type",
10
+ "description": "Type of entity this note is related to",
11
+ "placeholder": "Select entity type..."
12
+ },
13
+ "entityId": {
14
+ "label": "Related Entity",
15
+ "description": "Entity this note is related to",
16
+ "placeholder": "Select entity..."
17
+ },
18
+ "title": {
19
+ "label": "Title",
20
+ "description": "Note title or subject",
21
+ "placeholder": "Enter title..."
22
+ },
23
+ "content": {
24
+ "label": "Content",
25
+ "description": "Note content",
26
+ "placeholder": "Enter note content..."
27
+ },
28
+ "type": {
29
+ "label": "Note Type",
30
+ "description": "Type or category of note",
31
+ "placeholder": "Select type..."
32
+ },
33
+ "isPrivate": {
34
+ "label": "Private Note",
35
+ "description": "Whether this note is private"
36
+ },
37
+ "isPinned": {
38
+ "label": "Pinned",
39
+ "description": "Whether this note is pinned"
40
+ },
41
+ "tags": {
42
+ "label": "Tags",
43
+ "description": "Note tags for categorization",
44
+ "placeholder": "Enter tags..."
45
+ },
46
+ "attachments": {
47
+ "label": "Attachments",
48
+ "description": "File attachments"
49
+ },
50
+ "reminderDate": {
51
+ "label": "Reminder Date",
52
+ "description": "When to be reminded about this note",
53
+ "placeholder": "Select date..."
54
+ },
55
+ "sharedWith": {
56
+ "label": "Shared With",
57
+ "description": "Users this note is shared with",
58
+ "placeholder": "Select users..."
59
+ },
60
+ "createdAt": {
61
+ "label": "Created At",
62
+ "description": "When the note was created"
63
+ },
64
+ "updatedAt": {
65
+ "label": "Updated At",
66
+ "description": "When the note was last updated"
67
+ }
68
+ },
69
+ "options": {
70
+ "entityType": {
71
+ "lead": "Lead",
72
+ "contact": "Contact",
73
+ "company": "Company",
74
+ "opportunity": "Opportunity",
75
+ "activity": "Activity",
76
+ "campaign": "Campaign",
77
+ "product": "Product"
78
+ },
79
+ "type": {
80
+ "general": "General",
81
+ "meeting": "Meeting Notes",
82
+ "call": "Call Notes",
83
+ "email": "Email Notes",
84
+ "research": "Research",
85
+ "todo": "To-Do",
86
+ "idea": "Idea",
87
+ "feedback": "Feedback",
88
+ "issue": "Issue",
89
+ "solution": "Solution"
90
+ }
91
+ },
92
+ "actions": {
93
+ "create": "Create Note",
94
+ "edit": "Edit Note",
95
+ "delete": "Delete Note",
96
+ "view": "View Note",
97
+ "list": "List Notes",
98
+ "pin": "Pin Note",
99
+ "unpin": "Unpin Note",
100
+ "share": "Share Note",
101
+ "duplicate": "Duplicate Note",
102
+ "export": "Export Note"
103
+ },
104
+ "messages": {
105
+ "created": "Note created successfully",
106
+ "updated": "Note updated successfully",
107
+ "deleted": "Note deleted successfully",
108
+ "pinned": "Note pinned successfully",
109
+ "unpinned": "Note unpinned successfully",
110
+ "shared": "Note shared successfully",
111
+ "notFound": "Note not found",
112
+ "error": "An error occurred while processing the note"
113
+ }
114
+ }
@@ -0,0 +1,114 @@
1
+ {
2
+ "entity": {
3
+ "name": "Nota",
4
+ "plural": "Notas",
5
+ "description": "Gestiona notas y documentación"
6
+ },
7
+ "fields": {
8
+ "entityType": {
9
+ "label": "Tipo de Entidad",
10
+ "description": "Tipo de entidad relacionada",
11
+ "placeholder": "Seleccionar tipo..."
12
+ },
13
+ "entityId": {
14
+ "label": "Entidad Relacionada",
15
+ "description": "Entidad relacionada",
16
+ "placeholder": "Seleccionar entidad..."
17
+ },
18
+ "title": {
19
+ "label": "Título",
20
+ "description": "Título o asunto de la nota",
21
+ "placeholder": "Ingrese título..."
22
+ },
23
+ "content": {
24
+ "label": "Contenido",
25
+ "description": "Contenido de la nota",
26
+ "placeholder": "Ingrese contenido..."
27
+ },
28
+ "type": {
29
+ "label": "Tipo",
30
+ "description": "Tipo o categoría de nota",
31
+ "placeholder": "Seleccionar tipo..."
32
+ },
33
+ "isPrivate": {
34
+ "label": "Nota Privada",
35
+ "description": "Si esta nota es privada"
36
+ },
37
+ "isPinned": {
38
+ "label": "Fijada",
39
+ "description": "Si esta nota está fijada"
40
+ },
41
+ "tags": {
42
+ "label": "Etiquetas",
43
+ "description": "Etiquetas para categorización",
44
+ "placeholder": "Ingrese etiquetas..."
45
+ },
46
+ "attachments": {
47
+ "label": "Adjuntos",
48
+ "description": "Archivos adjuntos"
49
+ },
50
+ "reminderDate": {
51
+ "label": "Fecha de Recordatorio",
52
+ "description": "Cuándo recordar esta nota",
53
+ "placeholder": "Seleccionar fecha..."
54
+ },
55
+ "sharedWith": {
56
+ "label": "Compartida con",
57
+ "description": "Usuarios con quienes se comparte",
58
+ "placeholder": "Seleccionar usuarios..."
59
+ },
60
+ "createdAt": {
61
+ "label": "Creado el",
62
+ "description": "Cuándo se creó la nota"
63
+ },
64
+ "updatedAt": {
65
+ "label": "Actualizado el",
66
+ "description": "Cuándo se actualizó por última vez"
67
+ }
68
+ },
69
+ "options": {
70
+ "entityType": {
71
+ "lead": "Lead",
72
+ "contact": "Contacto",
73
+ "company": "Empresa",
74
+ "opportunity": "Oportunidad",
75
+ "activity": "Actividad",
76
+ "campaign": "Campaña",
77
+ "product": "Producto"
78
+ },
79
+ "type": {
80
+ "general": "General",
81
+ "meeting": "Notas de Reunión",
82
+ "call": "Notas de Llamada",
83
+ "email": "Notas de Email",
84
+ "research": "Investigación",
85
+ "todo": "Por Hacer",
86
+ "idea": "Idea",
87
+ "feedback": "Comentarios",
88
+ "issue": "Problema",
89
+ "solution": "Solución"
90
+ }
91
+ },
92
+ "actions": {
93
+ "create": "Crear Nota",
94
+ "edit": "Editar Nota",
95
+ "delete": "Eliminar Nota",
96
+ "view": "Ver Nota",
97
+ "list": "Listar Notas",
98
+ "pin": "Fijar Nota",
99
+ "unpin": "Desfijar Nota",
100
+ "share": "Compartir Nota",
101
+ "duplicate": "Duplicar Nota",
102
+ "export": "Exportar Nota"
103
+ },
104
+ "messages": {
105
+ "created": "Nota creada exitosamente",
106
+ "updated": "Nota actualizada exitosamente",
107
+ "deleted": "Nota eliminada exitosamente",
108
+ "pinned": "Nota fijada exitosamente",
109
+ "unpinned": "Nota desfijada exitosamente",
110
+ "shared": "Nota compartida exitosamente",
111
+ "notFound": "Nota no encontrada",
112
+ "error": "Error al procesar la nota"
113
+ }
114
+ }
@@ -0,0 +1,118 @@
1
+ -- ============================================================================
2
+ -- Notes Table Migration
3
+ -- CRM theme: Notes and comments on CRM records
4
+ -- Updated with team support and RLS
5
+ -- ============================================================================
6
+
7
+ -- ============================================
8
+ -- ENUM TYPES
9
+ -- ============================================
10
+ DO $$ BEGIN
11
+ CREATE TYPE note_type AS ENUM ('general', 'call', 'meeting', 'email', 'followup');
12
+ EXCEPTION
13
+ WHEN duplicate_object THEN null;
14
+ END $$;
15
+
16
+ -- ============================================
17
+ -- TABLE
18
+ -- ============================================
19
+ CREATE TABLE IF NOT EXISTS "notes" (
20
+ "id" TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
21
+
22
+ -- Note content
23
+ "title" VARCHAR(255),
24
+ "content" TEXT NOT NULL,
25
+ "type" note_type DEFAULT 'general',
26
+ "isPinned" BOOLEAN DEFAULT false,
27
+ "isPrivate" BOOLEAN DEFAULT false,
28
+
29
+ -- Polymorphic relation
30
+ "entityType" VARCHAR(50), -- lead, contact, company, opportunity, campaign
31
+ "entityId" UUID,
32
+
33
+ -- Direct relations (optional)
34
+ "contactId" TEXT REFERENCES "contacts"("id") ON DELETE SET NULL,
35
+ "companyId" TEXT REFERENCES "companies"("id") ON DELETE SET NULL,
36
+ "opportunityId" TEXT REFERENCES "opportunities"("id") ON DELETE SET NULL,
37
+
38
+ -- Attachments
39
+ "attachments" JSONB DEFAULT '[]',
40
+
41
+ -- Ownership
42
+ "userId" TEXT NOT NULL REFERENCES "users"("id") ON DELETE CASCADE,
43
+ "teamId" TEXT NOT NULL REFERENCES "teams"("id") ON DELETE CASCADE,
44
+
45
+ -- Timestamps
46
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
47
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW()
48
+ );
49
+
50
+ -- ============================================
51
+ -- INDEXES
52
+ -- ============================================
53
+ CREATE INDEX IF NOT EXISTS "notes_teamId_idx" ON "notes" ("teamId");
54
+ CREATE INDEX IF NOT EXISTS "notes_userId_idx" ON "notes" ("userId");
55
+ CREATE INDEX IF NOT EXISTS "notes_type_idx" ON "notes" ("type");
56
+ CREATE INDEX IF NOT EXISTS "notes_isPinned_idx" ON "notes" ("isPinned") WHERE "isPinned" = true;
57
+ CREATE INDEX IF NOT EXISTS "notes_entityType_entityId_idx" ON "notes" ("entityType", "entityId");
58
+ CREATE INDEX IF NOT EXISTS "notes_createdAt_idx" ON "notes" ("createdAt" DESC);
59
+
60
+ -- ============================================
61
+ -- RLS
62
+ -- ============================================
63
+ ALTER TABLE "notes" ENABLE ROW LEVEL SECURITY;
64
+
65
+ DROP POLICY IF EXISTS "notes_select_policy" ON "notes";
66
+ DROP POLICY IF EXISTS "notes_insert_policy" ON "notes";
67
+ DROP POLICY IF EXISTS "notes_update_policy" ON "notes";
68
+ DROP POLICY IF EXISTS "notes_delete_policy" ON "notes";
69
+
70
+ -- Private notes only visible to creator, others visible to team
71
+ CREATE POLICY "notes_select_policy" ON "notes"
72
+ FOR SELECT
73
+ USING (
74
+ (NOT "isPrivate" AND "teamId" = ANY(public.get_user_team_ids()))
75
+ OR ("isPrivate" AND "userId" = public.get_auth_user_id())
76
+ OR public.is_superadmin()
77
+ );
78
+
79
+ CREATE POLICY "notes_insert_policy" ON "notes"
80
+ FOR INSERT
81
+ WITH CHECK ("teamId" = ANY(public.get_user_team_ids()));
82
+
83
+ CREATE POLICY "notes_update_policy" ON "notes"
84
+ FOR UPDATE
85
+ USING (
86
+ "userId" = public.get_auth_user_id()
87
+ OR ("teamId" = ANY(public.get_user_team_ids()) AND NOT "isPrivate")
88
+ OR public.is_superadmin()
89
+ );
90
+
91
+ CREATE POLICY "notes_delete_policy" ON "notes"
92
+ FOR DELETE
93
+ USING (
94
+ "userId" = public.get_auth_user_id()
95
+ OR ("teamId" = ANY(public.get_user_team_ids()) AND NOT "isPrivate")
96
+ OR public.is_superadmin()
97
+ );
98
+
99
+ -- ============================================
100
+ -- TRIGGER updatedAt
101
+ -- ============================================
102
+ CREATE OR REPLACE FUNCTION update_notes_updated_at()
103
+ RETURNS TRIGGER AS $$
104
+ BEGIN
105
+ NEW."updatedAt" = NOW();
106
+ RETURN NEW;
107
+ END;
108
+ $$ LANGUAGE plpgsql;
109
+
110
+ DROP TRIGGER IF EXISTS notes_updated_at_trigger ON "notes";
111
+ CREATE TRIGGER notes_updated_at_trigger
112
+ BEFORE UPDATE ON "notes"
113
+ FOR EACH ROW
114
+ EXECUTE FUNCTION update_notes_updated_at();
115
+
116
+ COMMENT ON TABLE "notes" IS 'Notes and comments on CRM records';
117
+ COMMENT ON COLUMN "notes"."isPrivate" IS 'Private notes only visible to creator';
118
+ COMMENT ON COLUMN "notes"."attachments" IS 'JSONB array of attachment URLs';
@@ -0,0 +1,114 @@
1
+ -- Migration: 002_notes_metas.sql
2
+ -- Description: Contacts metas (table, indexes, RLS)
3
+ -- Date: 2025-09-27
4
+
5
+ -- ============================================
6
+ -- TABLE
7
+ -- ============================================
8
+ -- No DROP needed - removed automatically by parent table CASCADE
9
+ CREATE TABLE IF NOT EXISTS public."notes_metas" (
10
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
11
+ "entityId" TEXT NOT NULL REFERENCES public."notes"(id) ON DELETE CASCADE,
12
+ "metaKey" TEXT NOT NULL,
13
+ "metaValue" JSONB NOT NULL DEFAULT '{}'::jsonb,
14
+ "dataType" TEXT DEFAULT 'json',
15
+ "isPublic" BOOLEAN NOT NULL DEFAULT false,
16
+ "isSearchable" BOOLEAN NOT NULL DEFAULT false,
17
+ "createdAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
18
+ "updatedAt" TIMESTAMPTZ NOT NULL DEFAULT now(),
19
+ CONSTRAINT notes_metas_unique_key UNIQUE ("entityId", "metaKey")
20
+ );
21
+
22
+ COMMENT ON TABLE public."notes_metas" IS 'Contacts metadata table - stores additional key-value pairs for notes';
23
+ COMMENT ON COLUMN public."notes_metas"."entityId" IS 'Generic foreign key to parent note entity';
24
+ COMMENT ON COLUMN public."notes_metas"."metaKey" IS 'Metadata key name';
25
+ COMMENT ON COLUMN public."notes_metas"."metaValue" IS 'Metadata value as JSONB';
26
+ COMMENT ON COLUMN public."notes_metas"."dataType" IS 'Type hint for the value: json, string, number, boolean';
27
+ COMMENT ON COLUMN public."notes_metas"."isPublic" IS 'Whether this metadata is publicly readable';
28
+ COMMENT ON COLUMN public."notes_metas"."isSearchable" IS 'Whether this metadata is searchable';
29
+
30
+ -- ============================================
31
+ -- TRIGGER updatedAt (uses Better Auth function)
32
+ -- ============================================
33
+ DROP TRIGGER IF EXISTS notes_metas_set_updated_at ON public."notes_metas";
34
+ CREATE TRIGGER notes_metas_set_updated_at
35
+ BEFORE UPDATE ON public."notes_metas"
36
+ FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();
37
+
38
+ -- ============================================
39
+ -- INDEXES
40
+ -- ============================================
41
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_entity_id ON public."notes_metas"("entityId");
42
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_key ON public."notes_metas"("metaKey");
43
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_composite ON public."notes_metas"("entityId", "metaKey", "isPublic");
44
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_is_public ON public."notes_metas"("isPublic") WHERE "isPublic" = true;
45
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_is_searchable ON public."notes_metas"("isSearchable") WHERE "isSearchable" = true;
46
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_searchable_key ON public."notes_metas"("metaKey") WHERE "isSearchable" = true;
47
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_value_gin ON public."notes_metas" USING GIN ("metaValue");
48
+ CREATE INDEX IF NOT EXISTS idx_notes_metas_value_ops ON public."notes_metas" USING GIN ("metaValue" jsonb_path_ops);
49
+
50
+ -- ============================================
51
+ -- RLS
52
+ -- ============================================
53
+ ALTER TABLE public."notes_metas" ENABLE ROW LEVEL SECURITY;
54
+
55
+ -- Cleanup existing policies
56
+ DROP POLICY IF EXISTS "Users can view note metas" ON public."notes_metas";
57
+ DROP POLICY IF EXISTS "Users can create note metas" ON public."notes_metas";
58
+ DROP POLICY IF EXISTS "Users can update note metas" ON public."notes_metas";
59
+ DROP POLICY IF EXISTS "Users can delete note metas" ON public."notes_metas";
60
+
61
+ -- ============================
62
+ -- AUTHENTICATED USER POLICIES
63
+ -- ============================
64
+ -- Inherit permissions from parent entity
65
+ CREATE POLICY "Users can view note metas"
66
+ ON public."notes_metas"
67
+ FOR SELECT TO authenticated
68
+ USING (
69
+ EXISTS (
70
+ SELECT 1 FROM public."notes" c
71
+ WHERE c.id = notes_metas."entityId"
72
+ AND c."userId" = public.get_auth_user_id()
73
+ )
74
+ );
75
+
76
+ CREATE POLICY "Users can create note metas"
77
+ ON public."notes_metas"
78
+ FOR INSERT TO authenticated
79
+ WITH CHECK (
80
+ EXISTS (
81
+ SELECT 1 FROM public."notes" c
82
+ WHERE c.id = notes_metas."entityId"
83
+ AND c."userId" = public.get_auth_user_id()
84
+ )
85
+ );
86
+
87
+ CREATE POLICY "Users can update note metas"
88
+ ON public."notes_metas"
89
+ FOR UPDATE TO authenticated
90
+ USING (
91
+ EXISTS (
92
+ SELECT 1 FROM public."notes" c
93
+ WHERE c.id = notes_metas."entityId"
94
+ AND c."userId" = public.get_auth_user_id()
95
+ )
96
+ )
97
+ WITH CHECK (
98
+ EXISTS (
99
+ SELECT 1 FROM public."notes" c
100
+ WHERE c.id = notes_metas."entityId"
101
+ AND c."userId" = public.get_auth_user_id()
102
+ )
103
+ );
104
+
105
+ CREATE POLICY "Users can delete note metas"
106
+ ON public."notes_metas"
107
+ FOR DELETE TO authenticated
108
+ USING (
109
+ EXISTS (
110
+ SELECT 1 FROM public."notes" c
111
+ WHERE c.id = notes_metas."entityId"
112
+ AND c."userId" = public.get_auth_user_id()
113
+ )
114
+ );