feat: Major WhatsApp integration update with Odoo and pause/resume

## Frontend
- Add media display (images, audio, video, docs) in Inbox
- Add pause/resume functionality for WhatsApp accounts
- Fix media URLs to use nginx proxy (relative URLs)

## API Gateway
- Add /accounts/:id/pause and /accounts/:id/resume endpoints
- Fix media URL handling for browser access

## WhatsApp Core
- Add pauseSession() - disconnect without logout
- Add resumeSession() - reconnect using saved credentials
- Add media download and storage for incoming messages
- Serve media files via /media/ static route

## Odoo Module (odoo_whatsapp_hub)
- Add Chat Hub interface with DOLLARS theme (dark, 3-column layout)
- Add WhatsApp/DRRR theme switcher for chat view
- Add "ABRIR CHAT" button in conversation form
- Add send_message_from_chat() method
- Add security/ir.model.access.csv
- Fix CSS scoping to avoid breaking Odoo UI
- Update webhook to handle message events properly

## Documentation
- Add docs/CONTEXTO_DESARROLLO.md with complete project context

## Infrastructure
- Add whatsapp_media Docker volume
- Configure nginx proxy for /media/ route
- Update .gitignore to track src/sessions/ source files

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Claude AI
2026-01-30 20:48:56 +00:00
parent 1040debe2e
commit 5dd3499097
33 changed files with 3636 additions and 138 deletions

83
qr-viewer.html Normal file
View File

@@ -0,0 +1,83 @@
<!DOCTYPE html>
<html>
<head>
<title>WhatsApp QR Scanner</title>
<style>
body { font-family: Arial; text-align: center; padding: 20px; background: #1a1a2e; color: white; }
#qr { margin: 20px auto; max-width: 300px; }
#qr img { width: 100%; border-radius: 10px; }
#status { padding: 10px; margin: 10px; border-radius: 5px; }
.connecting { background: #f39c12; }
.connected { background: #27ae60; }
.disconnected { background: #e74c3c; }
button { padding: 15px 30px; font-size: 18px; cursor: pointer; margin: 10px; border: none; border-radius: 5px; }
#createBtn { background: #27ae60; color: white; }
#refreshBtn { background: #3498db; color: white; }
</style>
</head>
<body>
<h1>WhatsApp QR Scanner</h1>
<p>Servidor: <strong>192.168.10.221:3001</strong></p>
<button id="createBtn" onclick="createSession()">Crear Sesión</button>
<button id="refreshBtn" onclick="checkStatus()">Actualizar Estado</button>
<div id="status">Esperando...</div>
<div id="qr"></div>
<div id="phone"></div>
<script>
const API = 'http://192.168.10.221:3001/api';
const SESSION_ID = 'odoo_whatsapp';
async function createSession() {
document.getElementById('status').className = 'connecting';
document.getElementById('status').textContent = 'Creando sesión...';
try {
const res = await fetch(`${API}/sessions`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ accountId: SESSION_ID, name: 'Odoo WhatsApp' })
});
const data = await res.json();
console.log('Sesión creada:', data);
// Esperar y verificar QR
setTimeout(checkStatus, 2000);
setTimeout(checkStatus, 5000);
setTimeout(checkStatus, 10000);
setTimeout(checkStatus, 15000);
} catch (e) {
document.getElementById('status').textContent = 'Error: ' + e.message;
}
}
async function checkStatus() {
try {
const res = await fetch(`${API}/sessions/${SESSION_ID}`);
const data = await res.json();
document.getElementById('status').textContent = `Estado: ${data.status}`;
document.getElementById('status').className = data.status;
if (data.qrCode) {
document.getElementById('qr').innerHTML = `<img src="${data.qrCode}" alt="QR Code">`;
document.getElementById('qr').innerHTML += '<p>Escanea este código con WhatsApp</p>';
} else if (data.status === 'connected') {
document.getElementById('qr').innerHTML = '<h2>✅ Conectado!</h2>';
document.getElementById('phone').textContent = 'Teléfono: ' + (data.phoneNumber || 'N/A');
} else {
document.getElementById('qr').innerHTML = '<p>Esperando QR...</p>';
}
} catch (e) {
document.getElementById('status').textContent = 'Error al verificar: ' + e.message;
}
}
// Auto-refresh cada 3 segundos
setInterval(checkStatus, 3000);
checkStatus();
</script>
</body>
</html>