# 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 Sincronizar Contactos y Grupos WhatsApp Web code model.sync_ww_contacts_groups() 1 hours ``` ### 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}") ```