Просмотр исходного кода

Merge commit 'd8b06773a605e46f9e257586a49b9cea64e4f638' into TC

odoo 2 месяцев назад
Родитель
Сommit
c4351047cd

+ 0 - 53
whatsapp_web/.gitignore

@@ -1,53 +0,0 @@
-# Python
-__pycache__/
-*.py[cod]
-*$py.class
-*.so
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-
-# Odoo
-*.pot
-*.mo
-*.log
-
-# IDE
-.idea/
-.vscode/
-*.swp
-*.swo
-*~
-.DS_Store
-
-# Environment
-.env
-.venv
-env/
-venv/
-ENV/
-
-# Testing
-.pytest_cache/
-.coverage
-htmlcov/
-
-# Temporary files
-*.tmp
-*.bak
-*.orig
-
-

+ 0 - 466
whatsapp_web/API_REFERENCE.md

@@ -1,466 +0,0 @@
-# API Reference - WhatsApp Web Integration
-
-## Índice
-- [Modelos](#modelos)
-- [Métodos](#métodos)
-- [Campos](#campos)
-- [Ejemplos de Uso](#ejemplos-de-uso)
-- [Respuestas de API](#respuestas-de-api)
-
-## Modelos
-
-### whatsapp.account (Extendido)
-
-Extensión del modelo base de cuentas WhatsApp para soportar WhatsApp Web.
-
-#### Campos Adicionales
-
-| Campo | Tipo | Descripción |
-|-------|------|-------------|
-| `whatsapp_web_url` | Char | URL del servidor whatsapp-web.js |
-
-#### Métodos
-
-##### `get_groups()`
-Obtiene la lista de grupos disponibles en WhatsApp Web.
-
-**Parámetros:** Ninguno
-
-**Retorna:** `list` - Lista de diccionarios con información de grupos
-
-**Ejemplo:**
-```python
-account = self.env['whatsapp.account'].browse(1)
-groups = account.get_groups()
-```
-
-**Respuesta esperada:**
-```python
-[
-    {
-        'id': {'_serialized': '120363158956331133@g.us'},
-        'name': 'Mi Grupo',
-        'members': [
-            {
-                'id': {'_serialized': '5215551234567@c.us'},
-                'number': '5551234567',
-                'name': 'Juan Pérez',
-                'isAdmin': False,
-                'isSuperAdmin': False
-            }
-        ]
-    }
-]
-```
-
----
-
-### whatsapp.message (Extendido)
-
-Extensión del modelo base de mensajes WhatsApp para soportar grupos.
-
-#### Campos Adicionales
-
-| Campo | Tipo | Descripción |
-|-------|------|-------------|
-| `recipient_type` | Selection | Tipo de destinatario: 'phone' o 'group' |
-| `whatsapp_group_id` | Many2one | Referencia al grupo (modelo ww.group) |
-| `final_recipient` | Char | Destinatario final calculado |
-
-#### Métodos
-
-##### `_compute_final_recipient()`
-Calcula el destinatario final basado en el tipo de destinatario.
-
-**Parámetros:** Ninguno (compute method)
-
-**Retorna:** `str` - ID del destinatario final
-
-##### `_get_final_destination()`
-Método mejorado para obtener destino final (grupo o teléfono).
-
-**Parámetros:** Ninguno
-
-**Retorna:** `str|False` - ID del destino final o False
-
-**Ejemplo:**
-```python
-message = self.env['whatsapp.message'].browse(1)
-destination = message._get_final_destination()
-```
-
-##### `_send_message(with_commit=False)`
-Override del método de envío para soportar WhatsApp Web.
-
-**Parámetros:**
-- `with_commit` (bool): Si hacer commit después del envío
-
-**Retorna:** `None`
-
-**Ejemplo:**
-```python
-message = self.env['whatsapp.message'].browse(1)
-message._send_message(with_commit=True)
-```
-
----
-
-### whatsapp.composer (Extendido)
-
-Extensión del composer de WhatsApp para soportar grupos y mensajes libres.
-
-#### Campos Adicionales
-
-| Campo | Tipo | Descripción |
-|-------|------|-------------|
-| `recipient_type` | Selection | Tipo de destinatario: 'phone' o 'group' |
-| `whatsapp_group_id` | Many2one | Referencia al grupo |
-| `whatsapp_group_id_char` | Char | ID de grupo manual |
-| `body` | Html | Mensaje libre (sin plantilla) |
-
-#### Métodos
-
-##### `_check_recipient_configuration()`
-Valida la configuración del destinatario en el composer.
-
-**Parámetros:** Ninguno (constraint method)
-
-**Retorna:** `None` (raise ValidationError si hay error)
-
-##### `_compute_invalid_phone_number_count()`
-Override para casos específicos de grupos.
-
-**Parámetros:** Ninguno (compute method)
-
-**Retorna:** `None`
-
-##### `_onchange_recipient_type()`
-Limpia campos al cambiar tipo de destinatario.
-
-**Parámetros:** Ninguno (onchange method)
-
-**Retorna:** `None`
-
-##### `action_send_whatsapp_template()`
-Override del método de envío para casos específicos de WhatsApp Web.
-
-**Parámetros:** Ninguno
-
-**Retorna:** `dict` - Acción de ventana o resultado del envío
-
-##### `_send_whatsapp_web_message()`
-Envía mensaje WhatsApp Web sin plantilla.
-
-**Parámetros:** Ninguno
-
-**Retorna:** `dict` - Acción de cierre de ventana
-
-**Ejemplo:**
-```python
-composer = self.env['whatsapp.composer'].create({
-    'recipient_type': 'group',
-    'whatsapp_group_id': group.id,
-    'body': 'Hola grupo!'
-})
-result = composer._send_whatsapp_web_message()
-```
-
-##### `_prepare_whatsapp_message_values(record)`
-Override para agregar información de grupo.
-
-**Parámetros:**
-- `record` (recordset): Registro relacionado
-
-**Retorna:** `dict` - Valores para crear mensaje WhatsApp
-
-##### `_get_whatsapp_web_account()`
-Obtiene cuenta de WhatsApp Web disponible.
-
-**Parámetros:** Ninguno
-
-**Retorna:** `whatsapp.account` - Cuenta WhatsApp Web
-
-**Ejemplo:**
-```python
-composer = self.env['whatsapp.composer'].create({})
-account = composer._get_whatsapp_web_account()
-```
-
-##### `_send_whatsapp_message_without_template(body, phone=None, group_id=None)`
-Envía mensaje de WhatsApp sin plantilla.
-
-**Parámetros:**
-- `body` (str): Contenido del mensaje
-- `phone` (str, opcional): Número de teléfono
-- `group_id` (str, opcional): ID del grupo
-
-**Retorna:** `whatsapp.message` - Mensaje creado
-
-**Ejemplo:**
-```python
-composer = self.env['whatsapp.composer'].create({})
-message = composer._send_whatsapp_message_without_template(
-    body="Mensaje de prueba",
-    group_id="120363158956331133@g.us"
-)
-```
-
----
-
-## Ejemplos de Uso
-
-### Envío de Mensaje Individual
-
-```python
-# Crear composer para número individual
-composer = self.env['whatsapp.composer'].create({
-    'recipient_type': 'phone',
-    'phone': '+525551234567',
-    'wa_template_id': template.id,
-    'res_model': 'res.partner',
-    'res_id': partner.id
-})
-
-# Enviar mensaje
-result = composer.action_send_whatsapp_template()
-```
-
-### Envío de Mensaje a Grupo
-
-```python
-# Crear composer para grupo
-composer = self.env['whatsapp.composer'].create({
-    'recipient_type': 'group',
-    'whatsapp_group_id': group.id,
-    'body': '<p>Hola grupo! Este es un mensaje libre.</p>',
-    'res_model': 'ww.group',
-    'res_id': group.id
-})
-
-# Enviar mensaje
-result = composer.action_send_whatsapp_template()
-```
-
-### Envío con Adjunto
-
-```python
-# Crear attachment
-attachment = self.env['ir.attachment'].create({
-    'name': 'documento.pdf',
-    'type': 'binary',
-    'datas': base64.b64encode(pdf_content),
-    'mimetype': 'application/pdf'
-})
-
-# Crear composer con adjunto
-composer = self.env['whatsapp.composer'].create({
-    'recipient_type': 'phone',
-    'phone': '+525551234567',
-    'body': 'Adjunto documento importante',
-    'attachment_id': attachment.id,
-    'res_model': 'res.partner',
-    'res_id': partner.id
-})
-
-# Enviar mensaje
-result = composer.action_send_whatsapp_template()
-```
-
-### Obtener Grupos de WhatsApp Web
-
-```python
-# Obtener cuenta WhatsApp Web
-account = self.env['whatsapp.account'].search([
-    ('whatsapp_web_url', '!=', False)
-], limit=1)
-
-# Obtener grupos
-groups = account.get_groups()
-
-# Procesar grupos
-for group_data in groups:
-    group_id = group_data['id']['_serialized']
-    group_name = group_data['name']
-    members = group_data.get('members', [])
-    
-    print(f"Grupo: {group_name} ({group_id})")
-    print(f"Miembros: {len(members)}")
-```
-
-### Envío Directo de Mensaje
-
-```python
-# Crear mensaje directamente
-message = self.env['whatsapp.message'].create({
-    'recipient_type': 'group',
-    'whatsapp_group_id': group.id,
-    'mobile_number': group.whatsapp_web_id,
-    'body': 'Mensaje directo al grupo',
-    'wa_account_id': account.id,
-    'state': 'outgoing'
-})
-
-# Enviar mensaje
-message._send_message(with_commit=True)
-```
-
-## Respuestas de API
-
-### Respuesta Exitosa de Envío
-
-```json
-{
-    "_data": {
-        "id": {
-            "_serialized": "3EB0C767D26A3D1B7B4A_5215551234567@c.us"
-        }
-    }
-}
-```
-
-### Respuesta de Grupos
-
-```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
-            }
-        ]
-    }
-]
-```
-
-### Respuesta de Error
-
-```json
-{
-    "error": "Método no implementado",
-    "code": 501
-}
-```
-
-## Códigos de Estado HTTP
-
-| Código | Descripción |
-|--------|-------------|
-| 200 | Petición exitosa |
-| 400 | Error en los parámetros |
-| 401 | No autorizado |
-| 404 | Endpoint no encontrado |
-| 500 | Error interno del servidor |
-| 501 | Método no implementado |
-
-## Formato de Números
-
-### Números Individuales
-- **Formato de entrada:** `+525551234567`, `5551234567`, `5215551234567`
-- **Formato interno:** `5215551234567@c.us`
-- **Reglas de formateo:**
-  - Se eliminan espacios, guiones y el símbolo +
-  - Si empieza con "52" y tiene 12 dígitos, se agrega "1"
-  - Si tiene 10 dígitos, se agrega "521"
-  - Se agrega sufijo "@c.us"
-
-### Grupos
-- **Formato:** `120363158956331133@g.us`
-- **Identificación:** Termina en "@g.us"
-- **No se aplica formateo:** Se usa tal como viene de WhatsApp Web
-
-## Manejo de Errores
-
-### Excepciones Comunes
-
-#### ValidationError
-```python
-from odoo.exceptions import ValidationError
-
-try:
-    composer.action_send_whatsapp_template()
-except ValidationError as e:
-    print(f"Error de validación: {e}")
-```
-
-#### ConnectionError
-```python
-import requests
-
-try:
-    response = requests.post(url, data=payload, headers=headers)
-except requests.exceptions.ConnectionError:
-    print("Error de conexión con el servidor WhatsApp Web")
-```
-
-### Logs de Debugging
-
-```python
-import logging
-_logger = logging.getLogger(__name__)
-
-# Log de envío exitoso
-_logger.info('WHATSAPP WEB SEND MESSAGE: %s', url)
-
-# Log de error
-_logger.error("Error en la petición de groups: %s", response.text)
-```
-
-## Configuración de Timeouts
-
-```python
-# Configurar timeout para requests
-import requests
-
-response = requests.post(
-    url, 
-    data=payload, 
-    headers=headers,
-    timeout=30  # 30 segundos
-)
-```
-
-## Rate Limiting
-
-El módulo incluye delays aleatorios entre envíos:
-
-```python
-import time
-import random
-
-# Delay aleatorio entre 3-7 segundos
-time.sleep(random.randint(3, 7))
-```
-

+ 0 - 225
whatsapp_web/README.md

@@ -1,225 +0,0 @@
-# WhatsApp Web Integration for Odoo 18
-
-## Descripción
-
-Este módulo integra WhatsApp con la automatización de marketing de Odoo, permitiendo enviar mensajes de WhatsApp a través de una URL personalizada usando whatsapp-web.js. Proporciona funcionalidad completa para enviar mensajes tanto a números telefónicos como a grupos de WhatsApp.
-
-## Características Principales
-
-- ✅ Envío de mensajes a números telefónicos individuales
-- ✅ Envío de mensajes a grupos de WhatsApp
-- ✅ Soporte para plantillas de WhatsApp Business API
-- ✅ Mensajes de texto libre (sin plantilla) para WhatsApp Web
-- ✅ Gestión de adjuntos y archivos multimedia
-- ✅ Respuestas a mensajes (quoted messages)
-- ✅ Integración con el sistema de marketing de Odoo
-- ✅ Configuración flexible de cuentas WhatsApp Web
-
-## Requisitos
-
-- Odoo 18.0
-- Módulo `whatsapp` (dependencia)
-- Servidor whatsapp-web.js configurado y funcionando
-
-## Instalación
-
-1. **Instalar el módulo:**
-   ```bash
-   cd /var/odoo/mcteam.run
-   sudo -u odoo venv/bin/python3 src/odoo-bin -c odoo.conf -i whatsapp_web
-   ```
-
-2. **Reiniciar el servidor Odoo:**
-   ```bash
-   ./restart_odoo.sh
-   ```
-
-## Configuración
-
-### 1. Configuración General
-
-1. Ir a **Configuración > Técnico > Parámetros del Sistema**
-2. Buscar el parámetro `whatsapp_web.whatsapp_endpoint`
-3. Establecer la URL de tu servidor whatsapp-web.js (ej: `https://web.whatsapp.com/api`)
-
-### 2. Configuración de Cuenta WhatsApp
-
-1. Ir a **WhatsApp > Configuración > Cuentas WhatsApp**
-2. Crear o editar una cuenta WhatsApp
-3. En la sección "WhatsApp Web", configurar:
-   - **WhatsApp Web URL**: URL de tu servidor whatsapp-web.js
-4. Guardar la configuración
-
-### 3. Configuración de Grupos (Opcional)
-
-Si deseas enviar mensajes a grupos, instala también el módulo `whatsapp_web_groups`:
-
-```bash
-sudo -u odoo venv/bin/python3 src/odoo-bin -c odoo.conf -i whatsapp_web_groups
-```
-
-## Uso
-
-### Envío de Mensajes Individuales
-
-1. **Desde el Composer de WhatsApp:**
-   - Seleccionar "Phone Number" como tipo de destinatario
-   - Ingresar el número de teléfono
-   - Elegir una plantilla o escribir mensaje libre
-   - Enviar
-
-2. **Desde Marketing:**
-   - Crear una campaña de marketing
-   - Configurar plantillas WhatsApp
-   - Ejecutar campaña
-
-### Envío de Mensajes a Grupos
-
-1. **Desde el Composer:**
-   - Seleccionar "WhatsApp Group" como tipo de destinatario
-   - Elegir un grupo de la lista o ingresar ID manualmente
-   - Escribir mensaje libre o usar plantilla
-   - Enviar
-
-2. **Desde la vista de Grupos:**
-   - Ir a **WhatsApp Web > Grupos**
-   - Seleccionar un grupo
-   - Hacer clic en "Send WhatsApp Message"
-
-### Formato de Números
-
-- **Números individuales**: Se formatean automáticamente con prefijo de país (ej: `5215551234567@c.us`)
-- **Grupos**: Usan formato `120363158956331133@g.us`
-
-### Mensajes con Adjuntos
-
-El módulo soporta envío de:
-- Documentos PDF
-- Imágenes
-- Videos
-- Audio
-- Cualquier tipo de archivo soportado por WhatsApp
-
-## API del Módulo
-
-### Métodos Principales
-
-#### `whatsapp.account.get_groups()`
-Obtiene la lista de grupos disponibles en WhatsApp Web.
-
-```python
-account = self.env['whatsapp.account'].browse(1)
-groups = account.get_groups()
-```
-
-#### `whatsapp.message._send_message()`
-Envía un mensaje WhatsApp usando la configuración de la cuenta.
-
-#### `whatsapp.composer.action_send_whatsapp_template()`
-Procesa el envío desde el composer, soportando tanto plantillas como mensajes libres.
-
-## Estructura de Datos
-
-### Modelos Principales
-
-#### `whatsapp.account` (Extendido)
-- `whatsapp_web_url`: URL del servidor whatsapp-web.js
-
-#### `whatsapp.message` (Extendido)
-- `recipient_type`: Tipo de destinatario ('phone' o 'group')
-- `whatsapp_group_id`: Referencia al grupo (si aplica)
-- `final_recipient`: Destinatario final calculado
-
-#### `whatsapp.composer` (Extendido)
-- `recipient_type`: Tipo de destinatario
-- `whatsapp_group_id`: Grupo seleccionado
-- `whatsapp_group_id_char`: ID de grupo manual
-- `body`: Mensaje libre (sin plantilla)
-
-## Configuración del Servidor whatsapp-web.js
-
-### Ejemplo de Configuración
-
-```javascript
-// Ejemplo de servidor básico
-const express = require('express');
-const { Client } = require('whatsapp-web.js');
-
-const app = express();
-app.use(express.json());
-
-const client = new Client();
-
-client.on('ready', () => {
-    console.log('WhatsApp Web está listo');
-});
-
-// Endpoint para enviar mensajes
-app.post('/', async (req, res) => {
-    const { method, args } = req.body;
-    
-    if (method === 'sendMessage') {
-        const [to, content, options] = args;
-        const message = await client.sendMessage(to, content, options);
-        res.json({ _data: { id: { _serialized: message.id._serialized } } });
-    }
-});
-
-client.initialize();
-app.listen(3000);
-```
-
-## Solución de Problemas
-
-### Error: "No WhatsApp Web account configured"
-- Verificar que la cuenta WhatsApp tenga configurada la URL de WhatsApp Web
-- Confirmar que la URL sea accesible desde el servidor Odoo
-
-### Error: "Error en la petición de groups"
-- Verificar conectividad con el servidor whatsapp-web.js
-- Confirmar que el servidor esté ejecutándose y respondiendo
-- Revisar logs del servidor para errores específicos
-
-### Mensajes no se envían
-- Verificar que WhatsApp Web esté conectado en el servidor
-- Confirmar que el número de teléfono esté en formato correcto
-- Revisar logs de Odoo para errores de la API
-
-### Grupos no aparecen
-- Ejecutar sincronización manual desde el menú de grupos
-- Verificar que el método `getGroups` esté implementado en el servidor
-- Confirmar permisos de lectura de grupos en WhatsApp
-
-## Logs y Debugging
-
-Los logs se pueden encontrar en:
-- **Logs de Odoo**: `/var/odoo/stg2.mcteam.run/logs/odoo-server.log`
-- **Logs del servidor whatsapp-web.js**: Revisar configuración del servidor
-
-Para debugging, buscar en los logs:
-- `WHATSAPP WEB SEND MESSAGE`
-- `Error en la petición`
-- `Petición exitosa`
-
-## Actualizaciones
-
-Para actualizar el módulo:
-
-```bash
-cd /var/odoo/mcteam.run
-sudo -u odoo venv/bin/python3 src/odoo-bin -c odoo.conf -u whatsapp_web
-./restart_odoo.sh
-```
-
-## Soporte
-
-Para soporte técnico o reportar bugs, contactar al equipo de desarrollo.
-
-## Changelog
-
-### Versión 1.0
-- Implementación inicial
-- Soporte para mensajes individuales y grupos
-- Integración con whatsapp-web.js
-- Soporte para plantillas y mensajes libres
-

+ 0 - 661
whatsapp_web/TROUBLESHOOTING.md

@@ -1,661 +0,0 @@
-# Guía de Solución de Problemas - WhatsApp Web Integration
-
-## Índice
-- [Problemas de Configuración](#problemas-de-configuración)
-- [Problemas de Conexión](#problemas-de-conexión)
-- [Problemas de Envío de Mensajes](#problemas-de-envío-de-mensajes)
-- [Problemas de Grupos](#problemas-de-grupos)
-- [Problemas de Sincronización](#problemas-de-sincronización)
-- [Problemas de Rendimiento](#problemas-de-rendimiento)
-- [Logs y Debugging](#logs-y-debugging)
-
-## Problemas de Configuración
-
-### Error: "No WhatsApp Web account configured"
-
-**Síntomas:**
-- Mensaje de error al intentar enviar mensajes
-- No se pueden configurar grupos
-
-**Causas:**
-1. No se ha configurado la URL de WhatsApp Web en la cuenta
-2. La cuenta WhatsApp no tiene el campo `whatsapp_web_url` configurado
-3. El módulo `whatsapp_web` no está instalado
-
-**Soluciones:**
-```bash
-# 1. Verificar instalación del módulo
-cd /var/odoo/mcteam.run
-sudo -u odoo venv/bin/python3 src/odoo-bin shell -c odoo.conf
-
-# En el shell de Odoo:
-env['ir.module.module'].search([('name', '=', 'whatsapp_web')]).state
-```
-
-```python
-# 2. Verificar configuración de cuenta
-account = env['whatsapp.account'].search([], limit=1)
-print(f"URL configurada: {account.whatsapp_web_url}")
-```
-
-```bash
-# 3. Configurar cuenta manualmente desde base de datos
-su - postgres -c "psql stg2.mcteam.run -c \"UPDATE whatsapp_account SET whatsapp_web_url = 'https://tu-servidor.com/api' WHERE id = 1;\" | cat"
-```
-
-### Error: "Template is required for WhatsApp Business API"
-
-**Síntomas:**
-- No se pueden enviar mensajes libres (sin plantilla)
-- Error al usar composer sin plantilla
-
-**Causas:**
-1. No hay cuentas WhatsApp Web configuradas
-2. Se está intentando usar mensaje libre con API oficial
-
-**Soluciones:**
-```python
-# Verificar cuentas WhatsApp Web disponibles
-accounts = env['whatsapp.account'].search([('whatsapp_web_url', '!=', False)])
-print(f"Cuentas WhatsApp Web: {len(accounts)}")
-
-# Si no hay cuentas, configurar una
-account = env['whatsapp.account'].search([], limit=1)
-account.write({'whatsapp_web_url': 'https://tu-servidor.com/api'})
-```
-
-### Error: "Invalid recipient configuration"
-
-**Síntomas:**
-- Error al seleccionar destinatario en composer
-- Validación falla al enviar mensajes
-
-**Causas:**
-1. Tipo de destinatario no coincide con datos proporcionados
-2. Falta información requerida (teléfono o grupo)
-
-**Soluciones:**
-```python
-# Verificar configuración de destinatario
-composer = env['whatsapp.composer'].browse(composer_id)
-print(f"Tipo destinatario: {composer.recipient_type}")
-print(f"Teléfono: {composer.phone}")
-print(f"Grupo: {composer.whatsapp_group_id}")
-
-# Corregir configuración
-if composer.recipient_type == 'group' and not composer.whatsapp_group_id:
-    # Seleccionar grupo o cambiar a teléfono
-    composer.write({'recipient_type': 'phone'})
-```
-
-## Problemas de Conexión
-
-### Error: "Connection refused" o timeout
-
-**Síntomas:**
-- Timeouts en peticiones HTTP
-- Error de conexión al servidor whatsapp-web.js
-
-**Diagnóstico:**
-```bash
-# 1. Verificar conectividad desde servidor Odoo
-curl -X POST https://tu-servidor-whatsapp.com/api \
-  -H "Content-Type: application/json" \
-  -d '{"method": "ping", "args": []}'
-
-# 2. Verificar logs del servidor whatsapp-web.js
-tail -f /ruta/logs/whatsapp-web.log
-```
-
-**Soluciones:**
-```python
-# 1. Verificar URL en configuración
-account = env['whatsapp.account'].search([('whatsapp_web_url', '!=', False)], limit=1)
-print(f"URL configurada: {account.whatsapp_web_url}")
-
-# 2. Probar conectividad desde Odoo
-import requests
-try:
-    response = requests.post(account.whatsapp_web_url, 
-                           json={"method": "ping", "args": []}, 
-                           timeout=10)
-    print(f"Respuesta: {response.status_code}")
-except Exception as e:
-    print(f"Error de conexión: {e}")
-```
-
-### Error: "Invalid SSL certificate"
-
-**Síntomas:**
-- Error SSL al conectar con servidor HTTPS
-- Certificado no válido
-
-**Soluciones:**
-```python
-# Deshabilitar verificación SSL (solo para desarrollo)
-import requests
-requests.packages.urllib3.disable_warnings()
-
-response = requests.post(url, json=payload, verify=False)
-```
-
-```bash
-# Actualizar certificados del sistema
-sudo apt-get update
-sudo apt-get install ca-certificates
-```
-
-### Error: "Method not implemented"
-
-**Síntomas:**
-- El servidor responde pero no implementa métodos requeridos
-- Error 501 en peticiones
-
-**Diagnóstico:**
-```bash
-# Verificar métodos implementados en el servidor
-curl -X POST https://tu-servidor.com/api \
-  -H "Content-Type: application/json" \
-  -d '{"method": "getGroups", "args": []}'
-```
-
-**Soluciones:**
-```javascript
-// Implementar método getGroups en servidor whatsapp-web.js
-app.post('/', async (req, res) => {
-    const { method, args } = req.body;
-    
-    if (method === 'getGroups') {
-        try {
-            const chats = await client.getChats();
-            const groups = chats.filter(chat => chat.isGroup);
-            res.json(groups);
-        } catch (error) {
-            res.status(500).json({error: error.message});
-        }
-    }
-});
-```
-
-## Problemas de Envío de Mensajes
-
-### Error: "Message not sent"
-
-**Síntomas:**
-- Mensajes quedan en estado "outgoing"
-- No se reciben mensajes en WhatsApp
-
-**Diagnóstico:**
-```python
-# Verificar estado de mensajes
-messages = env['whatsapp.message'].search([
-    ('state', '=', 'outgoing'),
-    ('create_date', '>', '2024-01-01')
-])
-for msg in messages:
-    print(f"Mensaje {msg.id}: {msg.state} - {msg.mobile_number}")
-```
-
-**Soluciones:**
-```python
-# 1. Verificar configuración de cuenta
-message = env['whatsapp.message'].browse(message_id)
-print(f"Cuenta: {message.wa_account_id.name}")
-print(f"URL: {message.wa_account_id.whatsapp_web_url}")
-
-# 2. Reintentar envío
-message._send_message(with_commit=True)
-
-# 3. Verificar formato de número
-print(f"Número original: {message.mobile_number}")
-formatted = message._whatsapp_phone_format()
-print(f"Número formateado: {formatted}")
-```
-
-### Error: "Invalid phone number format"
-
-**Síntomas:**
-- Números no se formatean correctamente
-- Error en validación de teléfonos
-
-**Diagnóstico:**
-```python
-# Verificar formateo de números
-message = env['whatsapp.message'].browse(message_id)
-original = message.mobile_number
-formatted = message._whatsapp_phone_format()
-print(f"Original: {original}")
-print(f"Formateado: {formatted}")
-```
-
-**Soluciones:**
-```python
-# Corregir formato manualmente
-def format_phone_number(number):
-    # Limpiar número
-    clean = number.replace(' ', '').replace('+', '').replace('-', '')
-    
-    # Aplicar reglas de formateo
-    if clean.startswith("52") and len(clean) == 12:
-        clean = "521" + clean[2:]
-    elif len(clean) == 10:
-        clean = "521" + clean
-    
-    return clean + '@c.us'
-
-# Aplicar corrección
-message.write({
-    'mobile_number': format_phone_number(message.mobile_number)
-})
-```
-
-### Error: "Attachment upload failed"
-
-**Síntomas:**
-- Adjuntos no se envían
-- Error al procesar archivos
-
-**Diagnóstico:**
-```python
-# Verificar adjunto
-attachment = env['ir.attachment'].browse(attachment_id)
-print(f"Nombre: {attachment.name}")
-print(f"Tipo: {attachment.mimetype}")
-print(f"Tamaño: {attachment.file_size}")
-print(f"Datos: {len(attachment.raw) if attachment.raw else 'No hay datos'}")
-```
-
-**Soluciones:**
-```python
-# 1. Verificar tamaño de archivo (límite WhatsApp: 100MB)
-if attachment.file_size > 100 * 1024 * 1024:
-    print("Archivo demasiado grande")
-
-# 2. Verificar tipo MIME
-valid_types = ['image/', 'video/', 'audio/', 'application/pdf', 'text/']
-if not any(attachment.mimetype.startswith(t) for t in valid_types):
-    print(f"Tipo de archivo no soportado: {attachment.mimetype}")
-
-# 3. Regenerar datos del archivo
-attachment.write({'raw': base64.b64encode(file_content)})
-```
-
-## Problemas de Grupos
-
-### Error: "Group not found"
-
-**Síntomas:**
-- Grupos no aparecen en la lista
-- Error al seleccionar grupo
-
-**Diagnóstico:**
-```python
-# Verificar grupos disponibles
-groups = env['ww.group'].search([])
-print(f"Grupos en base de datos: {len(groups)}")
-
-# Verificar sincronización
-account = env['whatsapp.account'].search([('whatsapp_web_url', '!=', False)], limit=1)
-api_groups = account.get_groups()
-print(f"Grupos desde API: {len(api_groups)}")
-```
-
-**Soluciones:**
-```python
-# 1. Sincronizar grupos manualmente
-env['ww.group'].sync_ww_contacts_groups()
-
-# 2. Verificar respuesta de API
-for group_data in api_groups:
-    print(f"Grupo API: {group_data.get('name')} - {group_data.get('id', {}).get('_serialized')}")
-
-# 3. Crear grupo manualmente si es necesario
-group = env['ww.group'].create({
-    'name': 'Mi Grupo',
-    'whatsapp_web_id': '120363158956331133@g.us',
-    'whatsapp_account_id': account.id
-})
-```
-
-### Error: "No members in group"
-
-**Síntomas:**
-- Grupos sin contactos
-- Canales no se crean
-
-**Diagnóstico:**
-```python
-# Verificar miembros de grupo
-group = env['ww.group'].browse(group_id)
-print(f"Contactos en grupo: {len(group.contact_ids)}")
-
-# Verificar datos de API
-account = group.whatsapp_account_id
-api_groups = account.get_groups()
-for api_group in api_groups:
-    if api_group['id']['_serialized'] == group.whatsapp_web_id:
-        members = api_group.get('members', [])
-        print(f"Miembros en API: {len(members)}")
-        break
-```
-
-**Soluciones:**
-```python
-# 1. Sincronizar contactos del grupo
-group.write({'contact_ids': [(6, 0, contact_ids)]})
-
-# 2. Crear contactos si no existen
-for member in members:
-    contact = env['res.partner'].search([
-        ('whatsapp_web_id', '=', member['id']['_serialized'])
-    ])
-    
-    if not contact:
-        contact = env['res.partner'].create({
-            'name': member.get('name', 'Sin nombre'),
-            'mobile': member.get('number', ''),
-            'whatsapp_web_id': member['id']['_serialized']
-        })
-    
-    contact_ids.append(contact.id)
-```
-
-## Problemas de Sincronización
-
-### Error: "Sync failed" o timeout
-
-**Síntomas:**
-- Sincronización se interrumpe
-- Timeouts en operaciones largas
-
-**Diagnóstico:**
-```python
-# Verificar logs de sincronización
-import logging
-_logger = logging.getLogger(__name__)
-
-# Ejecutar sincronización con logging detallado
-try:
-    env['ww.group'].sync_ww_contacts_groups()
-except Exception as e:
-    _logger.error(f"Error en sincronización: {e}")
-```
-
-**Soluciones:**
-```python
-# 1. Sincronizar en lotes pequeños
-def sync_in_batches(batch_size=5):
-    accounts = env['whatsapp.account'].search([('whatsapp_web_url', '!=', False)])
-    
-    for account in accounts:
-        groups_data = account.get_groups()
-        
-        for i in range(0, len(groups_data), batch_size):
-            batch = groups_data[i:i + batch_size]
-            process_batch(batch, account)
-            env.cr.commit()  # Commit después de cada lote
-
-# 2. Aumentar timeout de requests
-import requests
-response = requests.post(url, json=payload, timeout=60)
-```
-
-### Error: "Duplicate contacts"
-
-**Síntomas:**
-- Contactos duplicados en grupos
-- Constraint violations
-
-**Diagnóstico:**
-```python
-# Buscar contactos duplicados
-contacts = env['res.partner'].search([
-    ('whatsapp_web_id', '!=', False)
-])
-
-# Verificar duplicados por número
-mobile_counts = {}
-for contact in contacts:
-    if contact.mobile:
-        mobile_counts[contact.mobile] = mobile_counts.get(contact.mobile, 0) + 1
-
-duplicates = {k: v for k, v in mobile_counts.items() if v > 1}
-print(f"Duplicados por móvil: {duplicates}")
-```
-
-**Soluciones:**
-```python
-# 1. Fusionar contactos duplicados
-def merge_duplicate_contacts():
-    contacts = env['res.partner'].search([('whatsapp_web_id', '!=', False)])
-    
-    for contact in contacts:
-        if contact.mobile and len(contact.mobile) >= 10:
-            last_10 = contact.mobile[-10:]
-            duplicates = env['res.partner'].search([
-                ('mobile', 'like', '%' + last_10),
-                ('id', '!=', contact.id)
-            ])
-            
-            if duplicates:
-                # Fusionar en el primer contacto
-                for dup in duplicates:
-                    dup.unlink()
-
-# 2. Limpiar relaciones duplicadas
-def cleanup_duplicate_relations():
-    relations = env['ww.group_contact_rel'].search([])
-    
-    for rel in relations:
-        duplicates = env['ww.group_contact_rel'].search([
-            ('group_id', '=', rel.group_id.id),
-            ('contact_id', '=', rel.contact_id.id),
-            ('id', '!=', rel.id)
-        ])
-        
-        if duplicates:
-            duplicates.unlink()
-```
-
-## Problemas de Rendimiento
-
-### Error: "Memory limit exceeded"
-
-**Síntomas:**
-- Procesos se terminan por falta de memoria
-- Sincronización falla con muchos datos
-
-**Soluciones:**
-```python
-# 1. Procesar en chunks más pequeños
-def process_large_dataset(data, chunk_size=100):
-    for i in range(0, len(data), chunk_size):
-        chunk = data[i:i + chunk_size]
-        process_chunk(chunk)
-        env.cr.commit()  # Commit frecuente
-        gc.collect()  # Liberar memoria
-
-# 2. Usar iteradores en lugar de listas
-def process_contacts_efficiently():
-    contacts = env['res.partner'].search([('whatsapp_web_id', '!=', False)])
-    
-    for contact in contacts:
-        process_contact(contact)
-        if contact.id % 100 == 0:  # Commit cada 100 registros
-            env.cr.commit()
-```
-
-### Error: "Database timeout"
-
-**Síntomas:**
-- Operaciones de base de datos timeout
-- Transacciones muy largas
-
-**Soluciones:**
-```python
-# 1. Usar transacciones más cortas
-def sync_with_short_transactions():
-    accounts = env['whatsapp.account'].search([])
-    
-    for account in accounts:
-        with env.cr.savepoint():  # Transacción por cuenta
-            sync_account_groups(account)
-            env.cr.commit()
-
-# 2. Deshabilitar autocommit para operaciones masivas
-def bulk_operations():
-    env.cr.autocommit = False
-    try:
-        # Operaciones masivas
-        bulk_create_contacts()
-        bulk_update_groups()
-        env.cr.commit()
-    except Exception:
-        env.cr.rollback()
-        raise
-    finally:
-        env.cr.autocommit = True
-```
-
-## Logs y Debugging
-
-### Configurar Logging Detallado
-
-```python
-# Configurar logging en código
-import logging
-_logger = logging.getLogger(__name__)
-
-# Logging detallado para debugging
-_logger.setLevel(logging.DEBUG)
-
-def debug_sync_process():
-    _logger.info("Iniciando sincronización")
-    
-    try:
-        # Proceso de sincronización
-        result = env['ww.group'].sync_ww_contacts_groups()
-        _logger.info(f"Sincronización completada: {result}")
-        
-    except Exception as e:
-        _logger.error(f"Error en sincronización: {e}", exc_info=True)
-```
-
-### Verificar Logs del Sistema
-
-```bash
-# Ver logs de Odoo en tiempo real
-tail -f /var/odoo/stg2.mcteam.run/logs/odoo-server.log | grep -i "whatsapp"
-
-# Filtrar por módulo específico
-tail -f /var/odoo/stg2.mcteam.run/logs/odoo-server.log | grep -i "whatsapp_web"
-
-# Buscar errores específicos
-grep -i "error\|exception\|traceback" /var/odoo/stg2.mcteam.run/logs/odoo-server.log | grep -i "whatsapp"
-```
-
-### Debug desde Shell de Odoo
-
-```bash
-# Acceder al shell de Odoo
-cd /var/odoo/mcteam.run
-sudo -u odoo venv/bin/python3 src/odoo-bin shell -c odoo.conf
-```
-
-```python
-# En el shell de Odoo
-# Verificar estado de módulos
-modules = env['ir.module.module'].search([('name', 'ilike', 'whatsapp')])
-for module in modules:
-    print(f"{module.name}: {module.state}")
-
-# Verificar configuración
-accounts = env['whatsapp.account'].search([])
-for account in accounts:
-    print(f"Cuenta: {account.name}")
-    print(f"URL: {account.whatsapp_web_url}")
-    print(f"Activa: {account.active}")
-
-# Probar conectividad
-import requests
-account = accounts[0]
-try:
-    response = requests.post(account.whatsapp_web_url, 
-                           json={"method": "ping", "args": []}, 
-                           timeout=10)
-    print(f"Respuesta: {response.status_code}")
-except Exception as e:
-    print(f"Error: {e}")
-```
-
-### Debug de Base de Datos
-
-```bash
-# Verificar registros en base de datos
-su - postgres -c "psql stg2.mcteam.run -c \"SELECT COUNT(*) FROM ww_group;\" | cat"
-
-# Verificar configuración de cuentas
-su - postgres -c "psql stg2.mcteam.run -c \"SELECT name, whatsapp_web_url FROM whatsapp_account;\" | cat"
-
-# Verificar mensajes pendientes
-su - postgres -c "psql stg2.mcteam.run -c \"SELECT COUNT(*) FROM whatsapp_message WHERE state = 'outgoing';\" | cat"
-```
-
-## Herramientas de Diagnóstico
-
-### Script de Diagnóstico Completo
-
-```python
-def diagnose_whatsapp_web():
-    """Script completo de diagnóstico"""
-    
-    print("=== DIAGNÓSTICO WHATSAPP WEB ===")
-    
-    # 1. Verificar módulos
-    print("\n1. Verificando módulos...")
-    modules = env['ir.module.module'].search([('name', 'ilike', 'whatsapp')])
-    for module in modules:
-        print(f"  {module.name}: {module.state}")
-    
-    # 2. Verificar cuentas
-    print("\n2. Verificando cuentas...")
-    accounts = env['whatsapp.account'].search([])
-    for account in accounts:
-        print(f"  {account.name}: {account.whatsapp_web_url or 'Sin URL'}")
-    
-    # 3. Verificar grupos
-    print("\n3. Verificando grupos...")
-    groups = env['ww.group'].search([])
-    print(f"  Total grupos: {len(groups)}")
-    for group in groups:
-        print(f"    {group.name}: {len(group.contact_ids)} contactos")
-    
-    # 4. Verificar mensajes
-    print("\n4. Verificando mensajes...")
-    messages = env['whatsapp.message'].search([])
-    print(f"  Total mensajes: {len(messages)}")
-    
-    outgoing = messages.filtered(lambda m: m.state == 'outgoing')
-    print(f"  Pendientes: {len(outgoing)}")
-    
-    # 5. Probar conectividad
-    print("\n5. Probando conectividad...")
-    web_account = accounts.filtered(lambda a: a.whatsapp_web_url)
-    if web_account:
-        try:
-            import requests
-            response = requests.post(web_account[0].whatsapp_web_url, 
-                                   json={"method": "ping", "args": []}, 
-                                   timeout=10)
-            print(f"  Conectividad: OK ({response.status_code})")
-        except Exception as e:
-            print(f"  Conectividad: ERROR - {e}")
-    else:
-        print("  Conectividad: No hay cuentas WhatsApp Web configuradas")
-
-# Ejecutar diagnóstico
-diagnose_whatsapp_web()
-```
-
-Este script proporciona una visión completa del estado del sistema y ayuda a identificar problemas comunes.
-

+ 2 - 4
whatsapp_web/__manifest__.py

@@ -9,11 +9,9 @@
     """,
     'author': 'Tu Nombre',
     'website': 'https://www.tuempresa.com',
-    'depends': ['whatsapp', 'marketing_automation_whatsapp'],
+    'depends': ['whatsapp'],
     'data': [
-        'views/whatsapp_account_views.xml',
-        'views/whatsapp_message_views.xml',
-        'views/whatsapp_composer_views.xml'
+        'views/whatsapp_account_views.xml'
     ],
     'installable': True,
     'auto_install': False,

BIN
whatsapp_web/__pycache__/__init__.cpython-310.pyc


+ 1 - 3
whatsapp_web/models/__init__.py

@@ -1,5 +1,3 @@
-from . import whatsapp_account
 from . import whatsapp_message
-from . import whatsapp_composer
 from . import whatsapp_patch
-from . import mail_message
+from . import whatsapp_account

BIN
whatsapp_web/models/__pycache__/__init__.cpython-310.pyc


BIN
whatsapp_web/models/__pycache__/marketing_activity.cpython-310.pyc


BIN
whatsapp_web/models/__pycache__/res_config_settings.cpython-310.pyc


BIN
whatsapp_web/models/__pycache__/whatsapp_message.cpython-310.pyc


+ 0 - 130
whatsapp_web/models/mail_message.py

@@ -1,130 +0,0 @@
-import logging
-import requests
-from odoo import models
-from odoo.addons.mail.tools.discuss import Store
-from odoo.addons.whatsapp.tools.whatsapp_exception import WhatsAppError
-from odoo.exceptions import UserError
-
-_logger = logging.getLogger(__name__)
-
-class MailMessage(models.Model):
-    _inherit = 'mail.message'
-
-    def _message_reaction(self, content, action, partner, guest, store: Store = None):
-        """Sobrescribir para usar WhatsApp Web API Gateway cuando esté configurado"""
-        # Si es mensaje de WhatsApp, verificar si usa WhatsApp Web
-        if self.message_type == "whatsapp_message" and self.wa_message_ids:
-            wa_msg = self.wa_message_ids[0]
-            
-            # Verificar si la cuenta usa WhatsApp Web
-            if wa_msg.wa_account_id and wa_msg.wa_account_id.whatsapp_web_url:
-                # Usar API Gateway para WhatsApp Web
-                self._send_whatsapp_web_reaction(wa_msg, content, action, partner, guest, store)
-                # Actualizar UI directamente usando el método base de mail (sin pasar por enterprise)
-                # Esto evita que el método del enterprise intente enviar de nuevo
-                return self._update_reaction_ui(content, action, partner, guest, store)
-            else:
-                # Usar método original para WhatsApp Business API (enterprise)
-                # Este llamará a super() al final para actualizar la UI
-                return super()._message_reaction(content, action, partner, guest, store)
-        
-        # Para mensajes que no son de WhatsApp, usar método base
-        return super()._message_reaction(content, action, partner, guest, store)
-    
-    def _update_reaction_ui(self, content, action, partner, guest, store: Store = None):
-        """Actualizar la UI de reacciones sin intentar enviar (para WhatsApp Web)"""
-        self.ensure_one()
-        # Buscar reacción existente
-        domain = [
-            ("message_id", "=", self.id),
-            ("partner_id", "=", partner.id),
-            ("guest_id", "=", guest.id),
-            ("content", "=", content),
-        ]
-        reaction = self.env["mail.message.reaction"].search(domain)
-        # Crear/eliminar reacción según la acción
-        if action == "add" and not reaction:
-            create_values = {
-                "message_id": self.id,
-                "content": content,
-                "partner_id": partner.id,
-                "guest_id": guest.id,
-            }
-            self.env["mail.message.reaction"].create(create_values)
-        if action == "remove" and reaction:
-            reaction.unlink()
-        if store:
-            # Llenar el store para usuarios portal no autenticados
-            self._reaction_group_to_store(store, content)
-        # Enviar el grupo de reacciones al bus para usuarios autenticados
-        self._bus_send_reaction_group(content)
-
-    def _send_whatsapp_web_reaction(self, wa_msg, content, action, partner, guest, store: Store = None):
-        """Enviar reacción usando WhatsApp Web API Gateway"""
-        self.ensure_one()
-        
-        account = wa_msg.wa_account_id
-        url = account.whatsapp_web_url
-        session_name = account.whatsapp_web_login
-        api_key = account.whatsapp_web_api_key
-        
-        if not all([url, session_name, api_key]):
-            raise UserError("WhatsApp Web no está completamente configurado. Faltan URL, Login o API Key.")
-        
-        # Manejar reacciones previas (igual que el método original)
-        if action == "add":
-            previous_reaction = self.env["mail.message.reaction"].search([
-                ("message_id", "=", self.id),
-                ("partner_id", "=", partner.id),
-                ("guest_id", "=", guest.id),
-            ], limit=1)
-            if previous_reaction:
-                previous_reaction_emoji = previous_reaction.content
-                if previous_reaction_emoji == content:
-                    return
-                previous_reaction.unlink()
-                self._bus_send_reaction_group(previous_reaction_emoji)
-        
-        # Obtener el ID del mensaje original
-        message_id = wa_msg.msg_uid
-        if not message_id:
-            raise UserError("No se puede enviar reacción: el mensaje no tiene ID válido.")
-        
-        # Construir URL y payload para la API Gateway
-        base_url = url.rstrip('/')
-        endpoint = 'send-reaction'
-        full_url = f"{base_url}/api/v1/{session_name}/{endpoint}"
-        
-        # Determinar emoji (vacío si es remover)
-        emoji = content if action == "add" else ""
-        
-        payload = {
-            "messageId": message_id,
-            "emoji": emoji
-        }
-        
-        headers = {
-            "Content-Type": "application/json",
-            "X-API-Key": api_key
-        }
-        
-        try:
-            _logger.info("Enviando reacción %s al mensaje %s", emoji or "vacía", message_id)
-            response = requests.post(full_url, json=payload, headers=headers, timeout=30)
-            
-            if response.status_code == 200:
-                _logger.info("Reacción enviada exitosamente a WhatsApp Web")
-                # No retornar aquí, dejar que el método padre actualice la UI
-                return
-            else:
-                error_text = response.text
-                _logger.error("Error al enviar reacción. Código: %s, Respuesta: %s", response.status_code, error_text)
-                raise UserError(f"Error al enviar reacción: {error_text}")
-                
-        except requests.exceptions.RequestException as e:
-            _logger.error("Error de conexión al enviar reacción: %s", str(e))
-            raise UserError(f"Error de conexión al enviar reacción: {str(e)}")
-        except Exception as e:
-            _logger.error("Error inesperado al enviar reacción: %s", str(e))
-            raise UserError(f"Error al enviar reacción: {str(e)}")
-

+ 2 - 51
whatsapp_web/models/whatsapp_account.py

@@ -1,59 +1,10 @@
 import logging
-import requests
-import json
 
 from odoo import fields, models
 
 _logger = logging.getLogger(__name__)
 
 class WhatsAppAccount(models.Model):
-    _inherit = 'whatsapp.account'
+    _inherit = ['whatsapp.account']
 
-    whatsapp_web_url = fields.Char(string="WhatsApp Web URL", readonly=False, copy=False)
-    whatsapp_web_login = fields.Char(string="Login", readonly=False, copy=False)
-    whatsapp_web_api_key = fields.Char(string="API Key", readonly=False, copy=False)
-
-    def get_groups(self):
-        """
-        Obtiene los grupos de WhatsApp Web para la cuenta desde la base de datos de la plataforma.
-        Returns:
-            list: Lista de diccionarios con la información de los grupos en formato compatible con Odoo
-        """
-        self.ensure_one()
-        
-        if not self.whatsapp_web_url:
-            _logger.warning("No se ha configurado la URL de WhatsApp Web para la cuenta %s", self.name)
-            return []
-
-        if not self.whatsapp_web_login:
-            _logger.warning("No se ha configurado el Login (session_name) para la cuenta %s", self.name)
-            return []
-
-        if not self.whatsapp_web_api_key:
-            _logger.warning("No se ha configurado la API Key para la cuenta %s", self.name)
-            return []
-
-        try:
-            # Construir URL del nuevo endpoint
-            base_url = self.whatsapp_web_url.rstrip('/')
-            session_name = self.whatsapp_web_login
-            url = f"{base_url}/api/v1/{session_name}/groups"
-            
-            headers = {
-                "Content-Type": "application/json",
-                "X-API-Key": self.whatsapp_web_api_key
-            }
-            
-            response = requests.get(url, headers=headers, timeout=30)
-            
-            if response.status_code == 200:
-                groups = response.json()
-                _logger.info("Grupos obtenidos desde la base de datos: %d grupos", len(groups))
-                return groups
-            else:
-                _logger.error("Error al obtener groups: %s - %s", response.status_code, response.text)
-                return []
-                
-        except Exception as e:
-            _logger.error("Error en la petición de groups: %s", str(e))
-            return []
+    whatsapp_web_url = fields.Char(string="WhatsApp Web URL", readonly=False, copy=False)

+ 0 - 217
whatsapp_web/models/whatsapp_composer.py

@@ -1,217 +0,0 @@
-from odoo import models, fields, api
-from odoo.exceptions import ValidationError
-import logging
-
-_logger = logging.getLogger(__name__)
-
-class WhatsAppComposer(models.TransientModel):
-    _inherit = 'whatsapp.composer'
-
-    # Campos para soporte básico de grupos (solo por ID string, sin Many2one)
-    # La funcionalidad completa de grupos con Many2one está en whatsapp_web_groups
-    recipient_type = fields.Selection([
-        ('phone', 'Phone Number'),
-        ('group', 'WhatsApp Group')
-    ], string='Send To', default='phone', help="Choose recipient type")
-    
-    # Campo para ID de grupo como string
-    whatsapp_group_id_char = fields.Char(string='Group ID', 
-                                         help="WhatsApp Group ID (e.g., 120363158956331133@g.us)")
-    
-    # Campo para mensaje libre (sin plantilla)
-    body = fields.Html(string='Message Body', help="Free text message (for WhatsApp Web accounts without template)")
-    
-    @api.constrains('recipient_type', 'phone', 'whatsapp_group_id_char', 'wa_template_id', 'body')
-    def _check_recipient_configuration(self):
-        """Validar configuración de destinatario en composer"""
-        for record in self:
-            # Si está en contexto de skip_template_validation, saltar validaciones de plantilla
-            if self.env.context.get('skip_template_validation'):
-                # Solo validar configuración básica de destinatario
-                if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
-                    raise ValidationError("Please enter a Group ID when sending to groups")
-                elif record.recipient_type == 'phone' and not record.phone:
-                    raise ValidationError("Please provide a phone number when sending to individuals")
-                return  # Saltar el resto de validaciones
-            
-            # Detectar si hay cuentas de WhatsApp Web disponibles
-            whatsapp_web_accounts = record.env['whatsapp.account'].search([
-                ('whatsapp_web_url', '!=', False)
-            ])
-            has_whatsapp_web = bool(whatsapp_web_accounts)
-            
-            if record.recipient_type == 'group' and not record.whatsapp_group_id_char:
-                raise ValidationError("Please enter a Group ID when sending to groups")
-            elif record.recipient_type == 'phone' and not record.phone:
-                raise ValidationError("Please provide a phone number when sending to individuals")
-            
-            # Validar que haya contenido (plantilla o mensaje libre)
-            if not record.wa_template_id and not record.body:
-                if has_whatsapp_web:
-                    raise ValidationError("Please provide either a template or write a free text message")
-                else:
-                    raise ValidationError("Template is required for WhatsApp Business API")
-            
-            # Si usa mensaje libre, debe haber WhatsApp Web
-            if record.body and not record.wa_template_id and not has_whatsapp_web:
-                raise ValidationError("Free text messages require WhatsApp Web account configuration")
-    
-    @api.depends('phone', 'batch_mode', 'recipient_type', 'whatsapp_group_id_char')
-    def _compute_invalid_phone_number_count(self):
-        """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
-        for composer in self:
-            # SOLO intervenir si es un caso muy específico de grupo
-            if (hasattr(composer, 'recipient_type') and composer.recipient_type == 'group'):
-                composer.invalid_phone_number_count = 0
-                continue
-                
-            # SOLO intervenir si el phone es explícitamente un ID de grupo
-            if composer.phone and composer.phone.endswith('@g.us'):
-                composer.invalid_phone_number_count = 0
-                continue
-                
-            # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
-            super(WhatsAppComposer, composer)._compute_invalid_phone_number_count()
-    
-    def action_send_whatsapp_template(self):
-        """Override del método de envío SOLO para casos específicos de WhatsApp Web sin plantilla"""
-        
-        # SOLO intervenir si es un caso muy específico:
-        # 1. No hay plantilla
-        # 2. Hay mensaje libre (body)
-        # 3. Es tipo grupo O hay WhatsApp Web disponible
-        whatsapp_web_accounts = self.env['whatsapp.account'].search([
-            ('whatsapp_web_url', '!=', False)
-        ])
-        has_whatsapp_web = bool(whatsapp_web_accounts)
-        
-        # CONDICIÓN MUY ESPECÍFICA para no interferir con funcionalidad nativa
-        is_special_case = (
-            not self.wa_template_id and  # Sin plantilla
-            self.body and  # Con mensaje libre
-            has_whatsapp_web and  # Con WhatsApp Web disponible
-            (self.recipient_type == 'group' or  # Es grupo
-             (hasattr(self, 'phone') and self.phone and self.phone.endswith('@g.us')))  # O es ID de grupo directo
-        )
-        
-        if is_special_case:
-            return self._send_whatsapp_web_message()
-        
-        # TODOS LOS DEMÁS CASOS: usar método original sin modificar
-        return super().action_send_whatsapp_template()
-    
-    def _send_whatsapp_web_message(self):
-        """Enviar mensaje WhatsApp Web sin plantilla - siguiendo lógica original"""
-        records = self._get_active_records()
-        
-        for record in records:
-            # Determinar destinatario
-            if self.recipient_type == 'group':
-                if self.whatsapp_group_id_char:
-                    mobile_number = self.whatsapp_group_id_char
-                else:
-                    raise ValidationError("Please specify a group ID")
-            else:
-                mobile_number = self.phone
-                if not mobile_number:
-                    raise ValidationError("Please provide a phone number")
-            
-            # Crear mail.message con adjuntos si existen (siguiendo lógica original)
-            post_values = {
-                'attachment_ids': [self.attachment_id.id] if self.attachment_id else [],
-                'body': self.body,
-                'message_type': 'whatsapp_message',
-                'partner_ids': hasattr(record, '_mail_get_partners') and record._mail_get_partners()[record.id].ids or record._whatsapp_get_responsible().partner_id.ids,
-            }
-            
-            if hasattr(records, '_message_log'):
-                message = record._message_log(**post_values)
-            else:
-                message = self.env['mail.message'].create(
-                    dict(post_values, res_id=record.id, model=self.res_model,
-                         subtype_id=self.env['ir.model.data']._xmlid_to_res_id("mail.mt_note"))
-                )
-            
-            # Crear mensaje WhatsApp (siguiendo estructura original)
-            whatsapp_message = self.env['whatsapp.message'].create({
-                'mail_message_id': message.id,
-                'mobile_number': mobile_number,
-                'mobile_number_formatted': mobile_number,
-                'recipient_type': self.recipient_type,
-                'wa_template_id': False,  # Sin plantilla
-                'wa_account_id': self._get_whatsapp_web_account().id,
-                'state': 'outgoing',
-            })
-            
-            # Enviar mensaje usando la lógica original de _send_message
-            whatsapp_message._send_message()
-        
-        return {'type': 'ir.actions.act_window_close'}
-    
-    def _prepare_whatsapp_message_values(self, record):
-        """Override SOLO para agregar información de grupo - NO interferir con funcionalidad nativa"""
-        
-        # SIEMPRE usar lógica original primero
-        values = super()._prepare_whatsapp_message_values(record)
-        
-        # SOLO agregar información de grupo si es caso específico
-        if (hasattr(self, 'recipient_type') and self.recipient_type == 'group'):
-            if self.whatsapp_group_id_char:
-                values.update({
-                    'recipient_type': 'group',
-                    'mobile_number': self.whatsapp_group_id_char,
-                    'mobile_number_formatted': self.whatsapp_group_id_char,
-                })
-        
-        # Siempre agregar recipient_type para compatibilidad
-        if not values.get('recipient_type'):
-            values['recipient_type'] = 'phone'
-        
-        return values
-    
-    def _get_whatsapp_web_account(self):
-        """Obtener cuenta de WhatsApp Web disponible"""
-        # Primero intentar usar la cuenta de la plantilla si existe
-        if self.wa_template_id and self.wa_template_id.wa_account_id and self.wa_template_id.wa_account_id.whatsapp_web_url:
-            return self.wa_template_id.wa_account_id
-            
-        # Si no, buscar cualquier cuenta con WhatsApp Web
-        account = self.env['whatsapp.account'].search([
-            ('whatsapp_web_url', '!=', False)
-        ], limit=1)
-        
-        if not account:
-            raise ValidationError("No WhatsApp Web account configured")
-            
-        return account
-    
-    def _send_whatsapp_message_without_template(self, body, phone=None, group_id=None):
-        """Enviar mensaje de WhatsApp sin plantilla (solo para WhatsApp Web)"""
-        # Solo funciona con WhatsApp Web, no con API oficial
-        if not (phone or group_id):
-            raise ValidationError("Debe especificar teléfono o grupo")
-            
-        # Crear mensaje directamente
-        message_vals = {
-            'body': body,
-            'mobile_number': group_id or phone,
-            'recipient_type': 'group' if group_id else 'phone',
-            'wa_template_id': False,  # Sin plantilla
-            'state': 'outgoing',
-        }
-        
-        # Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups
-            
-        # Crear mail.message
-        mail_message = self.env['mail.message'].create({
-            'body': body,
-            'message_type': 'whatsapp_message',
-        })
-        
-        message_vals['mail_message_id'] = mail_message.id
-        
-        # Crear y enviar mensaje WhatsApp
-        whatsapp_message = self.env['whatsapp.message'].create(message_vals)
-        whatsapp_message._send_message()
-        
-        return whatsapp_message

+ 57 - 244
whatsapp_web/models/whatsapp_message.py

@@ -1,6 +1,5 @@
 from odoo import models, fields, api
 from odoo.tools import groupby
-from odoo.exceptions import ValidationError
 import logging
 import markupsafe
 import requests
@@ -16,104 +15,38 @@ _logger = logging.getLogger(__name__)
 class WhatsAppMessage(models.Model):
     _inherit = 'whatsapp.message'
 
-    # Campos para soporte básico de grupos (solo por ID string, sin Many2one)
-    # La funcionalidad completa de grupos con Many2one está en whatsapp_web_groups
-    recipient_type = fields.Selection([
-        ('phone', 'Phone Number'),
-        ('group', 'WhatsApp Group')
-    ], string='Recipient Type', default='phone', help="Type of recipient: phone number or WhatsApp group")
-    
-    @api.depends('recipient_type', 'mobile_number')
-    def _compute_final_recipient(self):
-        """Compute the final recipient based on type"""
-        for record in self:
-            # Si es grupo y mobile_number termina en @g.us, usarlo directamente
-            if record.recipient_type == 'group' and record.mobile_number and record.mobile_number.endswith('@g.us'):
-                record.final_recipient = record.mobile_number
-            else:
-                record.final_recipient = record.mobile_number
-    
-    final_recipient = fields.Char('Final Recipient', compute='_compute_final_recipient', 
-                                  help="Final recipient (phone or group ID)")
-    
-    @api.depends('mobile_number', 'recipient_type')
-    def _compute_mobile_number_formatted(self):
-        """Override SOLO para casos específicos de grupos con WhatsApp Web"""
-        for message in self:
-            # SOLO intervenir si es grupo CON WhatsApp Web configurado
-            if (hasattr(message, 'recipient_type') and message.recipient_type == 'group' and
-                message.wa_account_id and message.wa_account_id.whatsapp_web_url and
-                message.mobile_number and message.mobile_number.endswith('@g.us')):
-                
-                message.mobile_number_formatted = message.mobile_number
-            else:
-                # TODOS LOS DEMÁS CASOS: usar lógica original sin modificar
-                super(WhatsAppMessage, message)._compute_mobile_number_formatted()
-    
-    @api.constrains('recipient_type', 'mobile_number')
-    def _check_recipient_configuration(self):
-        """Validar configuración de destinatario"""
-        for record in self:
-            if record.recipient_type == 'group':
-                if not (record.mobile_number and record.mobile_number.endswith('@g.us')):
-                    raise ValidationError("Para mensajes a grupos, debe proporcionar un ID de grupo válido (@g.us)")
-            elif record.recipient_type == 'phone':
-                if not record.mobile_number or record.mobile_number.endswith('@g.us'):
-                    raise ValidationError("Para mensajes a teléfonos, debe proporcionar un número telefónico válido")
-    
-    def _whatsapp_phone_format(self, fpath=None, number=None, raise_on_format_error=False):
-        """Override SOLO para casos específicos de grupos - NO interferir con funcionalidad nativa"""
-        self.ensure_one()
-        
-        # SOLO intervenir en casos muy específicos de grupos
-        # Si es un mensaje a grupo Y tiene WhatsApp Web configurado
-        if (hasattr(self, 'recipient_type') and self.recipient_type == 'group' and 
-            self.wa_account_id and self.wa_account_id.whatsapp_web_url):
-            
-            # Si el número es un ID de grupo (termina en @g.us), retornarlo sin validar
-            if number and number.endswith('@g.us'):
-                return number
-            elif self.mobile_number and self.mobile_number.endswith('@g.us'):
-                return self.mobile_number
-            
-        # TODOS LOS DEMÁS CASOS: usar validación original sin modificar
-        return super()._whatsapp_phone_format(fpath, number, raise_on_format_error)
-    
-    def _get_final_destination(self):
-        """Método mejorado para obtener destino final (grupo o teléfono)"""
-        self.ensure_one()
-        
-        # Si el mobile_number es un ID de grupo (termina en @g.us)
-        if self.mobile_number and self.mobile_number.endswith('@g.us'):
-            return self.mobile_number
-            
-        return False
     
     def _send_message(self, with_commit=False):
 
         url = ''
-        session_name = ''
-        api_key = ''
 
         if self.wa_account_id and self.wa_account_id.whatsapp_web_url:
             url = self.wa_account_id.whatsapp_web_url
-            session_name = self.wa_account_id.whatsapp_web_login or ''
-            api_key = self.wa_account_id.whatsapp_web_api_key or ''
-            _logger.info('WHATSAPP WEB SEND MESSAGE - URL: %s, Session: %s', url, session_name)
+            _logger.info('WHATSAPP WEB SEND MESSAGE' + url)
 
         group = ''
         
-        if not url or not session_name or not api_key:
-            # Si no hay configuración de WhatsApp Web, usar método original
+        if not url:
             super()._send_message(with_commit)
-            return
             
         for whatsapp_message in self:
-            # Determinar destinatario final usando solo la nueva lógica
-            final_destination = whatsapp_message._get_final_destination()
-            
-            if final_destination:
-                group = final_destination
+            #verificacion envio a grupo
+            #plantilla dada de alta en x_plantillas_whatsapp 
+            if 'marketing.trace' in self.env:
+                marketing_traces = self.env['marketing.trace'].sudo().search([('whatsapp_message_id', '=', whatsapp_message.id)])
+                for marketing_trace in marketing_traces:
+                    if 'x_studio_grupo_whatsapp' in marketing_trace.activity_id and marketing_trace.activity_id.x_studio_grupo_whatsapp:
+                        group = marketing_trace.activity_id.x_studio_grupo_whatsapp.x_studio_destinatario
+
+            if 'x_notificaciones_whats' in self.env and not group: 
+                notificaciones = self.env['x_notificaciones_whats'].sudo().search([('x_studio_plantilla_de_whatsapp', '=', whatsapp_message.wa_template_id.id)])
+                if notificaciones:
+                    _logger.info('template encontrado')
+                    if not group: 
+                        for notificacion in notificaciones:
+                            if not notificacion.x_studio_partner_unico:
+                                group = notificacion.x_studio_destinatario
+                                break
             
             attachment = False
 
@@ -141,14 +74,9 @@ class WhatsAppMessage(models.Model):
 
             #codigo para limpiar body y numero
             body = whatsapp_message.body
+            if isinstance(body, markupsafe.Markup):
+                text = html.unescape(str(body))
             
-            # Asegurar que body sea string y limpiar HTML
-            if body:
-                if isinstance(body, markupsafe.Markup):
-                    text = html.unescape(str(body))
-                else:
-                    text = str(body)
-                
                 # Reemplazamos las etiquetas BR y P
                 text = re.sub(r'<br\s*/?>|<BR\s*/?>', '\n', text)
                 text = re.sub(r'<p>|<P>', '\n\n', text)
@@ -162,172 +90,57 @@ class WhatsAppMessage(models.Model):
                 
                 # Limpiamos espacios en blanco al inicio y final
                 body = text.strip()
-                
-                # Asegurar que no esté vacío
-                if not body:
-                    body = "Mensaje de WhatsApp"
-            else:
-                body = "Mensaje de WhatsApp"
 
-            # Determinar número/destinatario final
-            if group:
-                # Si ya hay un grupo determinado, usarlo
-                number = group
-            else:
-                # Formatear número según el tipo de destinatario
-                if whatsapp_message.recipient_type == 'group':
-                    if whatsapp_message.mobile_number and whatsapp_message.mobile_number.endswith('@g.us'):
-                        number = whatsapp_message.mobile_number
-                    else:
-                        _logger.error("Mensaje configurado como grupo pero sin destinatario válido")
-                        continue
-                else:
-                    # Lógica original para números de teléfono
-                    number = whatsapp_message.mobile_number
-                    if number:
-                        number = number.replace(' ', '').replace('+','').replace('-','')
+            number = whatsapp_message.mobile_number
+            number = number.replace(' ', '').replace('+','')
 
-                        if number.startswith("52") and len(number) == 12:
-                            number = "521" + number[2:]
-                        
-                        if len(number) == 10:
-                            number = "521" + number
-                        
-                        number = number + '@c.us'
+            if number.startswith("52") and len(number) == 12:
+                number = "521" + number[2:]
+
+            # ENVIO DE MENSAJE
+            # Headers de la petición, si es necesario
+            headers = {
+                "Content-Type": "application/json"
+            }
 
-            # ENVIO DE MENSAJE - Nueva API Gateway
+            number = group if group else number + '@c.us'
+            
+            #$wa::sendMessage("521{$fields_data[$settings['borax_whatsapp_mobile']]}@c.us", ['type' => 'MessageMedia', 'args' => [mime_content_type($file), base64_encode(file_get_contents($file)), $filename, $filesize]], ['caption' => $borax_whatsapp_mensaje]);
             parent_message_id = ''
             if whatsapp_message.mail_message_id and whatsapp_message.mail_message_id.parent_id:
                     parent_id = whatsapp_message.mail_message_id.parent_id.wa_message_ids
                     if parent_id:
                         parent_message_id = parent_id[0].msg_uid
-            
-            # Validar que tenemos un destinatario válido
-            if not number:
-                _logger.error("No se pudo determinar el destinatario para el mensaje")
-                continue
-                
-            # Validar que tenemos un cuerpo de mensaje válido
-            if not body or not isinstance(body, str):
-                _logger.error("Cuerpo del mensaje inválido: %s", body)
-                body = "Mensaje de WhatsApp"
-            
-            # Determinar si es grupo
-            is_group = number.endswith('@g.us') if number else False
-            
-            # Construir URL base
-            base_url = url.rstrip('/')
-            endpoint = 'send-message'
-            
-            # Headers con autenticación
-            headers = {
-                "Content-Type": "application/json",
-                "X-API-Key": api_key
-            }
-            
-            # Preparar payload según tipo de mensaje
             if attachment:
-                # Determinar endpoint según tipo de archivo
-                mimetype = attachment.mimetype or 'application/octet-stream'
-                if mimetype.startswith('image/'):
-                    endpoint = 'send-image'
-                elif mimetype.startswith('video/'):
-                    endpoint = 'send-video'
-                elif mimetype.startswith('audio/'):
-                    endpoint = 'send-voice'
-                else:
-                    endpoint = 'send-file'
-                
-                # Convertir archivo a base64 con prefijo data URI
-                file_base64 = base64.b64encode(attachment.raw).decode('utf-8')
-                base64_with_prefix = f"data:{mimetype};base64,{file_base64}"
-                
                 payload = {
-                    "phone": [number],  # Array para send-image/send-file
-                    "base64": base64_with_prefix,
-                    "filename": attachment.name or "file",
-                    "caption": body,
-                    "isGroup": is_group
+                    "method": "sendMessage",  
+                    "args": [number, {'type': 'MessageMedia', 'args': [attachment.mimetype, base64.b64encode(attachment.raw).decode('utf-8'), attachment.name, attachment.file_size]}, {'caption': body}]
                 }
-                
-                if parent_message_id:
-                    payload["quotedMessageId"] = parent_message_id
-            else:
-                # Mensaje de texto
+            else: 
                 payload = {
-                    "phone": number,  # String para send-message
-                    "message": body,
-                    "isGroup": is_group
+                    "method": "sendMessage",  
+                    "args": [number, body, {}]
                 }
-                
-                if parent_message_id:
-                    payload["quotedMessageId"] = parent_message_id
-            
-            # Construir URL completa
-            full_url = f"{base_url}/api/v1/{session_name}/{endpoint}"
-            
-            # Log del payload para debugging
-            _logger.info("Enviando mensaje a %s (%s) usando endpoint %s", number, "grupo" if is_group else "contacto", endpoint)
-            
-            # Realizar petición POST
-            try:
-                response = requests.post(full_url, json=payload, headers=headers, timeout=60)
-                
-                # Procesar respuesta
-                if response.status_code == 200:
-                    try:
-                        response_json = response.json()
-                        
-                        # La nueva API puede devolver jobId (mensaje encolado) o id (enviado directamente)
-                        if 'jobId' in response_json:
-                            # Mensaje encolado - si la API devuelve jobId, significa que el mensaje fue aceptado
-                            # y está en proceso de envío, por lo que lo marcamos como 'sent'
-                            job_id = response_json.get('jobId')
-                            _logger.info("Mensaje aceptado por la API. Job ID: %s - Marcando como enviado", job_id)
-                            whatsapp_message.write({
-                                'state': 'sent',  # Marcar como enviado ya que fue aceptado por la API
-                                'msg_uid': job_id
-                            })
-                            self._cr.commit()
-                        elif 'id' in response_json:
-                            # Mensaje enviado directamente
-                            msg_id = response_json.get('id')
-                            if isinstance(msg_id, dict) and '_serialized' in msg_id:
-                                msg_uid = msg_id['_serialized']
-                            elif isinstance(msg_id, str):
-                                msg_uid = msg_id
-                            else:
-                                msg_uid = str(msg_id)
-                            
-                            _logger.info("Mensaje enviado exitosamente. ID: %s", msg_uid)
-                            whatsapp_message.write({
-                                'state': 'sent',
-                                'msg_uid': msg_uid
-                            })
-                            self._cr.commit()
-                        else:
-                            _logger.warning("Respuesta exitosa pero sin jobId ni id: %s", response_json)
-                            whatsapp_message.write({
-                                'state': 'outgoing'
-                            })
-                            self._cr.commit()
-                    except ValueError:
-                        _logger.error("La respuesta no es JSON válido: %s", response.text)
-                        whatsapp_message.write({
-                            'state': 'error'
-                        })
-                        self._cr.commit()
-                else:
-                    _logger.error("Error en la petición. Código: %s, Respuesta: %s", response.status_code, response.text)
+
+            if parent_message_id:
+                payload['args'][2]['quotedMessageId'] = parent_message_id
+
+            # Realizando la petición POST
+            response = requests.post(url, data=json.dumps(payload), headers=headers)
+
+            # Verificando si la respuesta contiene data->id
+            if response.status_code == 200:
+                response_json = response.json()
+                if "_data" in response_json and "id" in response_json["_data"]:
+                    _logger.info(f"Petición exitosa. ID: {response_json['_data']['id']['id']}")
                     whatsapp_message.write({
-                        'state': 'error'
+                        'state': 'sent',
+                        'msg_uid': response_json['_data']['id']['_serialized']
                     })
                     self._cr.commit()
-            except requests.exceptions.RequestException as e:
-                _logger.error("Error de conexión al enviar mensaje: %s", str(e))
-                whatsapp_message.write({
-                    'state': 'error'
-                })
-                self._cr.commit()
+                else:
+                    _logger.info("La respuesta no contiene 'data->id'.")
+            else:
+                _logger.info(f"Error en la petición. Código de estado: {response.status_code}")
 
             time.sleep(random.randint(3, 7))

+ 0 - 26
whatsapp_web/models/whatsapp_patch.py

@@ -21,29 +21,3 @@ def custom_get_whatsapp_document(self, document_id):
 
 # Sobrescribir el método en tiempo de ejecución
 WhatsAppApi._get_whatsapp_document = custom_get_whatsapp_document
-
-# Parche para el método _post_whatsapp_reaction para evitar errores de constraint
-try:
-    from odoo.addons.whatsapp.models.mail_message import MailMessage
-    
-    # Guardar referencia al método original
-    original_post_whatsapp_reaction = MailMessage._post_whatsapp_reaction
-    
-    def custom_post_whatsapp_reaction(self, reaction_content, partner_id):
-        """Parche para evitar error de constraint cuando partner_id es None"""
-        self.ensure_one()
-        
-        # Si no hay partner_id, no procesar la reacción
-        if not partner_id:
-            _logger.warning("Reacción de WhatsApp recibida sin partner_id para mensaje %s - ignorando", self.id)
-            return
-            
-        # Llamar al método original si hay partner_id
-        return original_post_whatsapp_reaction(self, reaction_content, partner_id)
-    
-    # Aplicar el parche
-    MailMessage._post_whatsapp_reaction = custom_post_whatsapp_reaction
-    _logger.info("Parche aplicado exitosamente para _post_whatsapp_reaction")
-    
-except ImportError as e:
-    _logger.warning("No se pudo aplicar el parche para _post_whatsapp_reaction: %s", e)

+ 1 - 3
whatsapp_web/views/whatsapp_account_views.xml

@@ -11,9 +11,7 @@
                     WhatsApp Web
                 </div>
                 <group>
-                    <field name="whatsapp_web_url" placeholder="https://wsrvb.crm.m22.mx" help="Base URL de la API Gateway"/>
-                    <field name="whatsapp_web_login" placeholder="mcteam" help="Nombre de sesión (session_name)"/>
-                    <field name="whatsapp_web_api_key" placeholder="API Key" password="True" help="API Key para autenticación en la API Gateway"/>
+                    <field name="whatsapp_web_url" placeholder="e.g. https://web.whatsapp.com/"/>
                 </group>
             </sheet>
         </field>

+ 0 - 47
whatsapp_web/views/whatsapp_composer_views.xml

@@ -1,47 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Extender vista de formulario del composer de WhatsApp -->
-        <record id="whatsapp_composer_view_form_groups" model="ir.ui.view">
-            <field name="name">whatsapp.composer.view.form.groups</field>
-            <field name="model">whatsapp.composer</field>
-            <field name="inherit_id" ref="whatsapp.whatsapp_composer_view_form"/>
-            <field name="arch" type="xml">
-                <!-- Agregar opción de envío a grupos -->
-                <xpath expr="//field[@name='phone']" position="before">
-                    <group string="Recipient Configuration" name="recipient_config">
-                        <field name="recipient_type" widget="radio" options="{'horizontal': true}"/>
-                    </group>
-                </xpath>
-                
-                <!-- Hacer wa_template_id opcional para WhatsApp Web -->
-                <xpath expr="//field[@name='wa_template_id']" position="attributes">
-                    <attribute name="help">Template required for WhatsApp Business API. Optional for WhatsApp Web accounts.</attribute>
-                </xpath>
-                
-                <!-- Modificar campo phone para que sea condicional -->
-                <xpath expr="//field[@name='phone']" position="attributes">
-                    <attribute name="invisible">recipient_type == 'group'</attribute>
-                    <attribute name="required">False</attribute>
-                </xpath>
-                
-                <!-- Agregar campos de grupo después del teléfono -->
-                <!-- Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups -->
-                <xpath expr="//field[@name='phone']" position="after">
-                    <field name="whatsapp_group_id_char" 
-                           invisible="recipient_type != 'group'"
-                           placeholder="Enter Group ID manually (e.g., 120363158956331133@g.us)"
-                           string="Group ID"/>
-                </xpath>
-                
-                <!-- Agregar campo de mensaje libre para WhatsApp Web -->
-                <xpath expr="//field[@name='wa_template_id']" position="after">
-                    <field name="body" 
-                           invisible="wa_template_id"
-                           placeholder="Write your free text message here (only for WhatsApp Web accounts)..."
-                           string="Free Text Message"/>
-                </xpath>
-            </field>
-        </record>
-    </data>
-</odoo>

+ 0 - 57
whatsapp_web/views/whatsapp_message_views.xml

@@ -1,57 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<odoo>
-    <data>
-        <!-- Extender vista de formulario de WhatsApp Message -->
-        <record id="whatsapp_message_view_form_groups" model="ir.ui.view">
-            <field name="name">whatsapp.message.view.form.groups</field>
-            <field name="model">whatsapp.message</field>
-            <field name="inherit_id" ref="whatsapp.whatsapp_message_view_form"/>
-            <field name="arch" type="xml">
-                <!-- Agregar campos de grupo después del campo mobile_number -->
-                <!-- Nota: El campo whatsapp_group_id Many2one está en whatsapp_web_groups -->
-                <xpath expr="//field[@name='mobile_number']" position="after">
-                    <field name="recipient_type" widget="radio" options="{'horizontal': true}"/>
-                    <field name="final_recipient" readonly="1" 
-                           invisible="not final_recipient"/>
-                </xpath>
-                
-                <!-- Hacer mobile_number invisible cuando es grupo -->
-                <xpath expr="//field[@name='mobile_number']" position="attributes">
-                    <attribute name="invisible">recipient_type == 'group'</attribute>
-                    <attribute name="required">recipient_type == 'phone'</attribute>
-                </xpath>
-            </field>
-        </record>
-
-        <!-- Extender vista de lista de WhatsApp Message -->
-        <record id="whatsapp_message_view_tree_groups" model="ir.ui.view">
-            <field name="name">whatsapp.message.view.tree.groups</field>
-            <field name="model">whatsapp.message</field>
-            <field name="inherit_id" ref="whatsapp.whatsapp_message_view_tree"/>
-            <field name="arch" type="xml">
-                <!-- Agregar columna de tipo de destinatario -->
-                <xpath expr="//field[@name='mobile_number']" position="after">
-                    <field name="recipient_type"/>
-                </xpath>
-            </field>
-        </record>
-
-        <!-- Extender vista de búsqueda -->
-        <record id="whatsapp_message_view_search_groups" model="ir.ui.view">
-            <field name="name">whatsapp.message.view.search.groups</field>
-            <field name="model">whatsapp.message</field>
-            <field name="inherit_id" ref="whatsapp.whatsapp_message_view_search"/>
-            <field name="arch" type="xml">
-                <!-- Agregar filtros para grupos al final -->
-                <xpath expr="//search" position="inside">
-                    <separator/>
-                    <filter string="Phone Messages" name="phone_messages" domain="[('recipient_type', '=', 'phone')]"/>
-                    <filter string="Group Messages" name="group_messages" domain="[('recipient_type', '=', 'group')]"/>
-                    <group expand="0" string="Group By">
-                        <filter string="Recipient Type" name="group_by_recipient_type" context="{'group_by': 'recipient_type'}"/>
-                    </group>
-                </xpath>
-            </field>
-        </record>
-    </data>
-</odoo>