Modelo principal para gestión de grupos de WhatsApp Web.
| 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 |
_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 WhatsAppRetorna: bool - True si se procesó correctamente
Ejemplo:
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:
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:
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:
# 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 mensajeattachment (ir.attachment, opcional): Archivo adjuntowa_template_id (whatsapp.template, opcional): Plantilla WhatsAppRetorna: whatsapp.message - Mensaje creado y enviado
Ejemplo:
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:
group = self.env['ww.group'].browse(1)
action = group.action_send_whatsapp_message()
# Retorna acción para abrir ventana del composer
Extensión del modelo de contactos para WhatsApp Web.
| Campo | Tipo | Descripción |
|---|---|---|
whatsapp_web_id |
Char | ID único del contacto en WhatsApp Web |
group_ids |
Many2many | Grupos donde participa el contacto |
ww.groupModelo para roles de miembros en grupos.
| Campo | Tipo | Descripción |
|---|---|---|
name |
Char | Nombre del rol (requerido) |
description |
Text | Descripción del rol |
Modelo de relación entre grupos y contactos con información adicional.
| 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 |
# 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'}")
# 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()
# 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 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()
# 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)
# 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
# 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)
])
[
{
"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 exitosa
{
'id': 123,
'name': '📱 Mi Grupo de Trabajo',
'channel_type': 'channel',
'member_count': 5
}
# Respuesta de error (False)
False
# Mensaje enviado exitosamente
{
'id': 456,
'state': 'sent',
'msg_uid': '3EB0C767D26A3D1B7B4A_120363158956331133@g.us',
'body': 'Mensaje importante!',
'recipient_type': 'group'
}
try:
group.send_whatsapp_message("Mensaje")
except ValueError as e:
print(f"Error: {e}") # "Group must have a WhatsApp account configured"
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}")
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")
<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>
# 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
})
# 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()
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
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()
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})
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}")