|
|
@@ -0,0 +1,583 @@
|
|
|
+# API Reference - Gestor de Grupos WhatsApp Web
|
|
|
+
|
|
|
+## Índice
|
|
|
+- [Modelos](#modelos)
|
|
|
+- [Métodos](#métodos)
|
|
|
+- [Campos](#campos)
|
|
|
+- [Ejemplos de Uso](#ejemplos-de-uso)
|
|
|
+- [Respuestas de API](#respuestas-de-api)
|
|
|
+
|
|
|
+## Modelos
|
|
|
+
|
|
|
+### ww.group
|
|
|
+
|
|
|
+Modelo principal para gestión de grupos de WhatsApp Web.
|
|
|
+
|
|
|
+#### Campos
|
|
|
+
|
|
|
+| Campo | Tipo | Descripción |
|
|
|
+|-------|------|-------------|
|
|
|
+| `name` | Char | Nombre del grupo (requerido) |
|
|
|
+| `whatsapp_web_id` | Char | ID único del grupo en WhatsApp Web |
|
|
|
+| `whatsapp_account_id` | Many2one | Cuenta de WhatsApp asociada |
|
|
|
+| `channel_id` | Many2one | Canal de discusión creado |
|
|
|
+| `contact_ids` | Many2many | Contactos miembros del grupo |
|
|
|
+
|
|
|
+#### Métodos
|
|
|
+
|
|
|
+##### `_process_messages(messages_data)`
|
|
|
+Procesa mensajes de WhatsApp y los crea en el canal de discusión.
|
|
|
+
|
|
|
+**Parámetros:**
|
|
|
+- `messages_data` (list): Lista de mensajes de WhatsApp
|
|
|
+
|
|
|
+**Retorna:** `bool` - True si se procesó correctamente
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+messages = [
|
|
|
+ {
|
|
|
+ 'id': {'_serialized': '3EB0C767D26A3D1B7B4A'},
|
|
|
+ 'body': 'Hola grupo!',
|
|
|
+ 'author': '5215551234567@c.us',
|
|
|
+ 'timestamp': 1640995200,
|
|
|
+ 'hasQuotedMsg': False
|
|
|
+ }
|
|
|
+]
|
|
|
+result = group._process_messages(messages)
|
|
|
+```
|
|
|
+
|
|
|
+##### `_create_discussion_channel()`
|
|
|
+Crea un canal de discusión para el grupo.
|
|
|
+
|
|
|
+**Parámetros:** Ninguno
|
|
|
+
|
|
|
+**Retorna:** `discuss.channel|False` - Canal creado o False si falla
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+channel = group._create_discussion_channel()
|
|
|
+if channel:
|
|
|
+ print(f"Canal creado: {channel.name}")
|
|
|
+```
|
|
|
+
|
|
|
+##### `_update_discussion_channel()`
|
|
|
+Actualiza los miembros del canal de discusión.
|
|
|
+
|
|
|
+**Parámetros:** Ninguno
|
|
|
+
|
|
|
+**Retorna:** `discuss.channel|False` - Canal actualizado o False si falla
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+channel = group._update_discussion_channel()
|
|
|
+```
|
|
|
+
|
|
|
+##### `sync_ww_contacts_groups()`
|
|
|
+Sincroniza todos los grupos y contactos desde WhatsApp Web.
|
|
|
+
|
|
|
+**Parámetros:** Ninguno (método de modelo)
|
|
|
+
|
|
|
+**Retorna:** `bool` - True si se sincronizó correctamente
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+# Sincronización completa
|
|
|
+self.env['ww.group'].sync_ww_contacts_groups()
|
|
|
+
|
|
|
+# Sincronización desde un grupo específico
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+# (Este método es estático, no se llama desde instancia)
|
|
|
+```
|
|
|
+
|
|
|
+##### `send_whatsapp_message(body, attachment=None, wa_template_id=None)`
|
|
|
+Envía un mensaje WhatsApp al grupo.
|
|
|
+
|
|
|
+**Parámetros:**
|
|
|
+- `body` (str): Contenido del mensaje
|
|
|
+- `attachment` (ir.attachment, opcional): Archivo adjunto
|
|
|
+- `wa_template_id` (whatsapp.template, opcional): Plantilla WhatsApp
|
|
|
+
|
|
|
+**Retorna:** `whatsapp.message` - Mensaje creado y enviado
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+attachment = self.env['ir.attachment'].browse(1)
|
|
|
+template = self.env['whatsapp.template'].browse(1)
|
|
|
+
|
|
|
+message = group.send_whatsapp_message(
|
|
|
+ body="Mensaje importante para el grupo",
|
|
|
+ attachment=attachment,
|
|
|
+ wa_template_id=template
|
|
|
+)
|
|
|
+```
|
|
|
+
|
|
|
+##### `action_send_whatsapp_message()`
|
|
|
+Abre el composer de WhatsApp para enviar mensaje al grupo.
|
|
|
+
|
|
|
+**Parámetros:** Ninguno
|
|
|
+
|
|
|
+**Retorna:** `dict` - Acción de ventana para abrir composer
|
|
|
+
|
|
|
+**Ejemplo:**
|
|
|
+```python
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+action = group.action_send_whatsapp_message()
|
|
|
+# Retorna acción para abrir ventana del composer
|
|
|
+```
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### ww.contact (Extiende res.partner)
|
|
|
+
|
|
|
+Extensión del modelo de contactos para WhatsApp Web.
|
|
|
+
|
|
|
+#### Campos Adicionales
|
|
|
+
|
|
|
+| Campo | Tipo | Descripción |
|
|
|
+|-------|------|-------------|
|
|
|
+| `whatsapp_web_id` | Char | ID único del contacto en WhatsApp Web |
|
|
|
+| `group_ids` | Many2many | Grupos donde participa el contacto |
|
|
|
+
|
|
|
+#### Relaciones
|
|
|
+
|
|
|
+- **group_ids**: Relación many2many con `ww.group`
|
|
|
+- **channel_ids**: Relación con canales de discusión
|
|
|
+- **meeting_ids**: Relación con eventos de calendario
|
|
|
+- **sla_ids**: Relación con SLAs de helpdesk
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### ww.role
|
|
|
+
|
|
|
+Modelo para roles de miembros en grupos.
|
|
|
+
|
|
|
+#### Campos
|
|
|
+
|
|
|
+| Campo | Tipo | Descripción |
|
|
|
+|-------|------|-------------|
|
|
|
+| `name` | Char | Nombre del rol (requerido) |
|
|
|
+| `description` | Text | Descripción del rol |
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+### ww.group_contact_rel
|
|
|
+
|
|
|
+Modelo de relación entre grupos y contactos con información adicional.
|
|
|
+
|
|
|
+#### Campos
|
|
|
+
|
|
|
+| Campo | Tipo | Descripción |
|
|
|
+|-------|------|-------------|
|
|
|
+| `group_id` | Many2one | Referencia al grupo |
|
|
|
+| `contact_id` | Many2one | Referencia al contacto |
|
|
|
+| `is_admin` | Boolean | Si es administrador del grupo |
|
|
|
+| `is_super_admin` | Boolean | Si es super administrador |
|
|
|
+| `role_id` | Many2one | Rol asignado en el grupo |
|
|
|
+
|
|
|
+#### Constraints
|
|
|
+
|
|
|
+- **group_contact_uniq**: Constraint único para evitar duplicados (group_id, contact_id)
|
|
|
+
|
|
|
+---
|
|
|
+
|
|
|
+## Ejemplos de Uso
|
|
|
+
|
|
|
+### Sincronización de Grupos
|
|
|
+
|
|
|
+```python
|
|
|
+# Sincronización completa
|
|
|
+self.env['ww.group'].sync_ww_contacts_groups()
|
|
|
+
|
|
|
+# Verificar grupos sincronizados
|
|
|
+groups = self.env['ww.group'].search([])
|
|
|
+for group in groups:
|
|
|
+ print(f"Grupo: {group.name}")
|
|
|
+ print(f"Miembros: {len(group.contact_ids)}")
|
|
|
+ print(f"Canal: {group.channel_id.name if group.channel_id else 'Sin canal'}")
|
|
|
+```
|
|
|
+
|
|
|
+### Creación Manual de Grupo
|
|
|
+
|
|
|
+```python
|
|
|
+# Crear grupo manualmente
|
|
|
+group = self.env['ww.group'].create({
|
|
|
+ 'name': 'Mi Grupo Personalizado',
|
|
|
+ 'whatsapp_web_id': '120363158956331133@g.us',
|
|
|
+ 'whatsapp_account_id': account.id
|
|
|
+})
|
|
|
+
|
|
|
+# Agregar contactos al grupo
|
|
|
+contacts = self.env['res.partner'].search([('whatsapp_web_id', '!=', False)])
|
|
|
+group.contact_ids = [(6, 0, contacts.ids)]
|
|
|
+
|
|
|
+# Crear canal de discusión
|
|
|
+channel = group._create_discussion_channel()
|
|
|
+```
|
|
|
+
|
|
|
+### Gestión de Contactos
|
|
|
+
|
|
|
+```python
|
|
|
+# Buscar contactos de WhatsApp Web
|
|
|
+contacts = self.env['res.partner'].search([
|
|
|
+ ('whatsapp_web_id', '!=', False)
|
|
|
+])
|
|
|
+
|
|
|
+# Agregar contacto a grupo
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+contact = self.env['res.partner'].browse(1)
|
|
|
+
|
|
|
+# Crear relación con rol
|
|
|
+rel = self.env['ww.group_contact_rel'].create({
|
|
|
+ 'group_id': group.id,
|
|
|
+ 'contact_id': contact.id,
|
|
|
+ 'is_admin': True,
|
|
|
+ 'role_id': admin_role.id
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+### Envío de Mensajes a Grupos
|
|
|
+
|
|
|
+```python
|
|
|
+# Envío directo
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+message = group.send_whatsapp_message("Mensaje importante!")
|
|
|
+
|
|
|
+# Envío con adjunto
|
|
|
+attachment = self.env['ir.attachment'].create({
|
|
|
+ 'name': 'documento.pdf',
|
|
|
+ 'type': 'binary',
|
|
|
+ 'datas': base64.b64encode(pdf_content),
|
|
|
+ 'mimetype': 'application/pdf'
|
|
|
+})
|
|
|
+
|
|
|
+message = group.send_whatsapp_message(
|
|
|
+ body="Adjunto documento importante",
|
|
|
+ attachment=attachment
|
|
|
+)
|
|
|
+
|
|
|
+# Envío usando composer
|
|
|
+action = group.action_send_whatsapp_message()
|
|
|
+```
|
|
|
+
|
|
|
+### Procesamiento de Mensajes
|
|
|
+
|
|
|
+```python
|
|
|
+# Procesar mensajes de un grupo
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+messages_data = [
|
|
|
+ {
|
|
|
+ 'id': {'_serialized': 'msg_id_123'},
|
|
|
+ 'body': 'Mensaje de prueba',
|
|
|
+ 'author': '5215551234567@c.us',
|
|
|
+ 'timestamp': 1640995200,
|
|
|
+ 'hasQuotedMsg': True,
|
|
|
+ 'quotedMsg': {
|
|
|
+ 'body': 'Mensaje citado',
|
|
|
+ 'author': '5215557654321@c.us'
|
|
|
+ }
|
|
|
+ }
|
|
|
+]
|
|
|
+
|
|
|
+result = group._process_messages(messages_data)
|
|
|
+```
|
|
|
+
|
|
|
+### Gestión de Roles
|
|
|
+
|
|
|
+```python
|
|
|
+# Crear roles
|
|
|
+admin_role = self.env['ww.role'].create({
|
|
|
+ 'name': 'Administrador',
|
|
|
+ 'description': 'Administrador del grupo'
|
|
|
+})
|
|
|
+
|
|
|
+member_role = self.env['ww.role'].create({
|
|
|
+ 'name': 'Miembro',
|
|
|
+ 'description': 'Miembro regular del grupo'
|
|
|
+})
|
|
|
+
|
|
|
+# Asignar roles a contactos en grupos
|
|
|
+group = self.env['ww.group'].browse(1)
|
|
|
+for contact in group.contact_ids:
|
|
|
+ rel = self.env['ww.group_contact_rel'].search([
|
|
|
+ ('group_id', '=', group.id),
|
|
|
+ ('contact_id', '=', contact.id)
|
|
|
+ ])
|
|
|
+
|
|
|
+ if contact.is_admin:
|
|
|
+ rel.role_id = admin_role.id
|
|
|
+ else:
|
|
|
+ rel.role_id = member_role.id
|
|
|
+```
|
|
|
+
|
|
|
+### Consultas Avanzadas
|
|
|
+
|
|
|
+```python
|
|
|
+# Grupos con más de 10 miembros
|
|
|
+large_groups = self.env['ww.group'].search([
|
|
|
+ ('contact_ids', '!=', False)
|
|
|
+])
|
|
|
+large_groups = [g for g in large_groups if len(g.contact_ids) > 10]
|
|
|
+
|
|
|
+# Contactos administradores
|
|
|
+admin_contacts = self.env['res.partner'].search([
|
|
|
+ ('group_ids', '!=', False)
|
|
|
+])
|
|
|
+admin_contacts = [c for c in admin_contacts
|
|
|
+ if any(rel.is_admin for rel in c.group_ids)]
|
|
|
+
|
|
|
+# Grupos sin canal
|
|
|
+groups_without_channel = self.env['ww.group'].search([
|
|
|
+ ('channel_id', '=', False)
|
|
|
+])
|
|
|
+```
|
|
|
+
|
|
|
+## Respuestas de API
|
|
|
+
|
|
|
+### Respuesta de getGroups()
|
|
|
+
|
|
|
+```json
|
|
|
+[
|
|
|
+ {
|
|
|
+ "id": {
|
|
|
+ "_serialized": "120363158956331133@g.us"
|
|
|
+ },
|
|
|
+ "name": "Mi Grupo de Trabajo",
|
|
|
+ "description": "Grupo para coordinación de proyectos",
|
|
|
+ "members": [
|
|
|
+ {
|
|
|
+ "id": {
|
|
|
+ "_serialized": "5215551234567@c.us"
|
|
|
+ },
|
|
|
+ "number": "5551234567",
|
|
|
+ "name": "Juan Pérez",
|
|
|
+ "pushname": "Juan",
|
|
|
+ "isAdmin": true,
|
|
|
+ "isSuperAdmin": false
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "id": {
|
|
|
+ "_serialized": "5215557654321@c.us"
|
|
|
+ },
|
|
|
+ "number": "5557654321",
|
|
|
+ "name": "María García",
|
|
|
+ "pushname": "María",
|
|
|
+ "isAdmin": false,
|
|
|
+ "isSuperAdmin": false
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ "messages": [
|
|
|
+ {
|
|
|
+ "id": {
|
|
|
+ "_serialized": "3EB0C767D26A3D1B7B4A"
|
|
|
+ },
|
|
|
+ "body": "Hola grupo!",
|
|
|
+ "author": "5215551234567@c.us",
|
|
|
+ "timestamp": 1640995200,
|
|
|
+ "hasQuotedMsg": false,
|
|
|
+ "quotedParticipant": null,
|
|
|
+ "quotedMsg": null
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+]
|
|
|
+```
|
|
|
+
|
|
|
+### Respuesta de Creación de Canal
|
|
|
+
|
|
|
+```python
|
|
|
+# Respuesta exitosa
|
|
|
+{
|
|
|
+ 'id': 123,
|
|
|
+ 'name': '📱 Mi Grupo de Trabajo',
|
|
|
+ 'channel_type': 'channel',
|
|
|
+ 'member_count': 5
|
|
|
+}
|
|
|
+
|
|
|
+# Respuesta de error (False)
|
|
|
+False
|
|
|
+```
|
|
|
+
|
|
|
+### Respuesta de Envío de Mensaje
|
|
|
+
|
|
|
+```python
|
|
|
+# Mensaje enviado exitosamente
|
|
|
+{
|
|
|
+ 'id': 456,
|
|
|
+ 'state': 'sent',
|
|
|
+ 'msg_uid': '3EB0C767D26A3D1B7B4A_120363158956331133@g.us',
|
|
|
+ 'body': 'Mensaje importante!',
|
|
|
+ 'recipient_type': 'group'
|
|
|
+}
|
|
|
+```
|
|
|
+
|
|
|
+## Manejo de Errores
|
|
|
+
|
|
|
+### Excepciones Comunes
|
|
|
+
|
|
|
+#### ValueError - Grupo sin cuenta
|
|
|
+```python
|
|
|
+try:
|
|
|
+ group.send_whatsapp_message("Mensaje")
|
|
|
+except ValueError as e:
|
|
|
+ print(f"Error: {e}") # "Group must have a WhatsApp account configured"
|
|
|
+```
|
|
|
+
|
|
|
+#### ValidationError - Datos inválidos
|
|
|
+```python
|
|
|
+from odoo.exceptions import ValidationError
|
|
|
+
|
|
|
+try:
|
|
|
+ group = self.env['ww.group'].create({
|
|
|
+ 'name': '', # Nombre vacío
|
|
|
+ 'whatsapp_web_id': 'invalid_id'
|
|
|
+ })
|
|
|
+except ValidationError as e:
|
|
|
+ print(f"Error de validación: {e}")
|
|
|
+```
|
|
|
+
|
|
|
+### Logs de Debugging
|
|
|
+
|
|
|
+```python
|
|
|
+import logging
|
|
|
+_logger = logging.getLogger(__name__)
|
|
|
+
|
|
|
+# Log de sincronización
|
|
|
+_logger.info(f"Procesando grupo {group_name}: {len(participants)} participantes encontrados")
|
|
|
+
|
|
|
+# Log de error
|
|
|
+_logger.error("Error en la sincronización de grupos para la cuenta %s: %s",
|
|
|
+ account.name, str(e))
|
|
|
+
|
|
|
+# Log de advertencia
|
|
|
+_logger.warning(f"Grupo {group_name} no tiene participantes en la respuesta de la API")
|
|
|
+```
|
|
|
+
|
|
|
+## Configuración de Cron Jobs
|
|
|
+
|
|
|
+### Configuración Básica
|
|
|
+
|
|
|
+```xml
|
|
|
+<record id="ir_cron_sync_ww_contacts_groups" model="ir.cron">
|
|
|
+ <field name="name">Sincronizar Contactos y Grupos WhatsApp Web</field>
|
|
|
+ <field name="model_id" ref="model_ww_group"/>
|
|
|
+ <field name="state">code</field>
|
|
|
+ <field name="code">model.sync_ww_contacts_groups()</field>
|
|
|
+ <field name="interval_number">1</field>
|
|
|
+ <field name="interval_type">hours</field>
|
|
|
+ <field name="active" eval="False"/>
|
|
|
+</record>
|
|
|
+```
|
|
|
+
|
|
|
+### Configuración Personalizada
|
|
|
+
|
|
|
+```python
|
|
|
+# Crear cron job personalizado
|
|
|
+cron = self.env['ir.cron'].create({
|
|
|
+ 'name': 'Sincronización Personalizada',
|
|
|
+ 'model_id': self.env.ref('whatsapp_web_groups.model_ww_group').id,
|
|
|
+ 'state': 'code',
|
|
|
+ 'code': 'model.sync_ww_contacts_groups()',
|
|
|
+ 'interval_number': 2,
|
|
|
+ 'interval_type': 'hours',
|
|
|
+ 'active': True,
|
|
|
+ 'user_id': self.env.user.id
|
|
|
+})
|
|
|
+```
|
|
|
+
|
|
|
+## Optimización de Performance
|
|
|
+
|
|
|
+### Sincronización en Lotes
|
|
|
+
|
|
|
+```python
|
|
|
+# Procesar grupos en lotes para evitar timeouts
|
|
|
+def sync_groups_in_batches(self, batch_size=10):
|
|
|
+ accounts = self.env['whatsapp.account'].search([])
|
|
|
+
|
|
|
+ for account in accounts:
|
|
|
+ groups_data = account.get_groups()
|
|
|
+
|
|
|
+ # Procesar en lotes
|
|
|
+ for i in range(0, len(groups_data), batch_size):
|
|
|
+ batch = groups_data[i:i + batch_size]
|
|
|
+ self._process_groups_batch(batch, account)
|
|
|
+
|
|
|
+ # Commit después de cada lote
|
|
|
+ self._cr.commit()
|
|
|
+```
|
|
|
+
|
|
|
+### Creación Bulk de Mensajes
|
|
|
+
|
|
|
+```python
|
|
|
+def _process_messages_bulk(self, messages_data):
|
|
|
+ """Procesar mensajes en bulk para mejor performance"""
|
|
|
+ if not messages_data:
|
|
|
+ return True
|
|
|
+
|
|
|
+ # Preparar valores para creación bulk
|
|
|
+ message_vals_list = []
|
|
|
+ for msg_data in messages_data:
|
|
|
+ message_vals = self._prepare_message_vals(msg_data)
|
|
|
+ message_vals_list.append(message_vals)
|
|
|
+
|
|
|
+ # Crear todos los mensajes de una vez
|
|
|
+ if message_vals_list:
|
|
|
+ self.env['mail.message'].create(message_vals_list)
|
|
|
+
|
|
|
+ return True
|
|
|
+```
|
|
|
+
|
|
|
+## Limpieza y Mantenimiento
|
|
|
+
|
|
|
+### Limpiar Grupos Vacíos
|
|
|
+
|
|
|
+```python
|
|
|
+def cleanup_empty_groups(self):
|
|
|
+ """Eliminar grupos sin contactos"""
|
|
|
+ empty_groups = self.env['ww.group'].search([
|
|
|
+ ('contact_ids', '=', False)
|
|
|
+ ])
|
|
|
+
|
|
|
+ if empty_groups:
|
|
|
+ _logger.info(f"Eliminando {len(empty_groups)} grupos vacíos")
|
|
|
+ empty_groups.unlink()
|
|
|
+```
|
|
|
+
|
|
|
+### Limpiar Contactos Huérfanos
|
|
|
+
|
|
|
+```python
|
|
|
+def cleanup_orphan_contacts(self):
|
|
|
+ """Limpiar contactos sin grupos"""
|
|
|
+ orphan_contacts = self.env['res.partner'].search([
|
|
|
+ ('whatsapp_web_id', '!=', False),
|
|
|
+ ('group_ids', '=', False)
|
|
|
+ ])
|
|
|
+
|
|
|
+ if orphan_contacts:
|
|
|
+ _logger.info(f"Limpiando {len(orphan_contacts)} contactos huérfanos")
|
|
|
+ orphan_contacts.write({'whatsapp_web_id': False})
|
|
|
+```
|
|
|
+
|
|
|
+### Regenerar Canales Perdidos
|
|
|
+
|
|
|
+```python
|
|
|
+def regenerate_missing_channels(self):
|
|
|
+ """Regenerar canales que se perdieron"""
|
|
|
+ groups_without_channel = self.env['ww.group'].search([
|
|
|
+ ('channel_id', '=', False),
|
|
|
+ ('contact_ids', '!=', False)
|
|
|
+ ])
|
|
|
+
|
|
|
+ for group in groups_without_channel:
|
|
|
+ try:
|
|
|
+ channel = group._create_discussion_channel()
|
|
|
+ if channel:
|
|
|
+ _logger.info(f"Canal regenerado para grupo {group.name}")
|
|
|
+ except Exception as e:
|
|
|
+ _logger.error(f"Error regenerando canal para {group.name}: {e}")
|
|
|
+```
|
|
|
+
|