up
This commit is contained in:
@@ -231,3 +231,11 @@
|
|||||||
[2026-01-28 14:04:21] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSREYzMUY2RUMwQTgxNERENTUwAA=="}]}
|
[2026-01-28 14:04:21] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSREYzMUY2RUMwQTgxNERENTUwAA=="}]}
|
||||||
[2026-01-28 14:04:21] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSREYzMUY2RUMwQTgxNERENTUwAA=="}]}
|
[2026-01-28 14:04:21] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSREYzMUY2RUMwQTgxNERENTUwAA=="}]}
|
||||||
[2026-01-28 14:04:21] Saving to DB - content: record_1769627059068.webm, media_url: 1602247974444552, local_file: uploads/media_697a5db44d08f0.23171792.ogg, local_thumb: null
|
[2026-01-28 14:04:21] Saving to DB - content: record_1769627059068.webm, media_url: 1602247974444552, local_file: uploads/media_697a5db44d08f0.23171792.ogg, local_thumb: null
|
||||||
|
[2026-01-29 11:39:39] Raw input: {"recipient":"573168950803","media_url":"http://localhost:8080/uploads/media_697b8d4b88a6c2.66006027.ogg","media_type":"audio","caption":null,"filename":"record_1769704778262.webm","is_voice":true}
|
||||||
|
[2026-01-29 11:39:39] Input decoded: {"recipient":"573168950803","media_url":"http:\/\/localhost:8080\/uploads\/media_697b8d4b88a6c2.66006027.ogg","media_type":"audio","caption":null,"filename":"record_1769704778262.webm","is_voice":true}
|
||||||
|
[2026-01-29 11:39:39] is_voice: true
|
||||||
|
[2026-01-29 11:39:40] Upload result: {"id":"906365935662402"}
|
||||||
|
[2026-01-29 11:39:40] Media ID obtained: 906365935662402
|
||||||
|
[2026-01-29 11:39:41] Send media by id response: {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
|
||||||
|
[2026-01-29 11:39:41] WhatsApp response (successful): {"messaging_product":"whatsapp","contacts":[{"input":"573168950803","wa_id":"573168950803"}],"messages":[{"id":"wamid.HBgMNTczMTY4OTUwODAzFQIAERgSNzAyNEVCRkRGNUYwM0IzQURGAA=="}]}
|
||||||
|
[2026-01-29 11:39:41] Saving to DB - content: record_1769704778262.webm, media_url: 906365935662402, local_file: uploads/media_697b8d4b88a6c2.66006027.ogg, local_thumb: null
|
||||||
|
|||||||
+1
-1
@@ -95,7 +95,7 @@ try {
|
|||||||
$lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? null;
|
$lastEventId = $_SERVER['HTTP_LAST_EVENT_ID'] ?? null;
|
||||||
$lastCheck = time();
|
$lastCheck = time();
|
||||||
$connectionStart = time();
|
$connectionStart = time();
|
||||||
$maxConnectionTime = 300; // 5 minutos máximo
|
$maxConnectionTime = 1800; // 30 minutos máximo (aumentado para evitar desconexiones frecuentes)
|
||||||
|
|
||||||
// Loop principal - revisar eventos cada 2 segundos
|
// Loop principal - revisar eventos cada 2 segundos
|
||||||
while (true) {
|
while (true) {
|
||||||
|
|||||||
+102
-61
@@ -826,10 +826,6 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
.unread-badge { margin-left: 8px; font-size: 12px; }
|
.unread-badge { margin-left: 8px; font-size: 12px; }
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
<link rel="manifest" href="/manifest.json">
|
|
||||||
<meta name="theme-color" content="#10b981">
|
|
||||||
<meta name="mobile-web-app-capable" content="yes">
|
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="chat-container">
|
<div class="chat-container">
|
||||||
@@ -839,7 +835,7 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
<div>
|
<div>
|
||||||
<h5 class="mb-0">💬 Conversaciones
|
<h5 class="mb-0">💬 Conversaciones
|
||||||
<span class="badge bg-warning text-dark ms-2" style="font-size: 9px; padding: 2px 6px; animation: pulse 2s infinite;">
|
<span class="badge bg-warning text-dark ms-2" style="font-size: 9px; padding: 2px 6px; animation: pulse 2s infinite;">
|
||||||
v1.2.1-<?php echo substr(time(), -4); ?>
|
v1.2.2-<?php echo substr(time(), -4); ?>
|
||||||
</span>
|
</span>
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
@@ -910,8 +906,6 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
</div>
|
</div>
|
||||||
<!-- Botón actualizar mensajes -->
|
<!-- Botón actualizar mensajes -->
|
||||||
<button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
|
<button class="btn btn-sm btn-outline-light" id="refresh-messages-btn" title="Actualizar mensajes"><i class="fas fa-sync-alt"></i></button>
|
||||||
<!-- PWA install button for conversations view -->
|
|
||||||
<button id="pwa-install-btn" class="btn btn-sm btn-outline-primary d-none" style="display:none;">Instalar</button>
|
|
||||||
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
|
<button class="btn btn-sm btn-outline-secondary" id="mark-unread-btn" title="Marcar conversación como no leída" style="display:none;">Marcar no leído</button>
|
||||||
<button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación" style="display:none;"><i class="fas fa-trash"></i></button>
|
<button class="btn btn-sm btn-outline-danger" id="delete-conversation-btn" title="Eliminar conversación" style="display:none;"><i class="fas fa-trash"></i></button>
|
||||||
</div>
|
</div>
|
||||||
@@ -1218,8 +1212,19 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
if (this.currentUserId && String(this.currentUserId) === String(userId)) {
|
if (this.currentUserId && String(this.currentUserId) === String(userId)) {
|
||||||
console.log('♻️ Mensaje para conversación ACTIVA');
|
console.log('♻️ Mensaje para conversación ACTIVA');
|
||||||
|
|
||||||
// SOLUCIÓN: Siempre usar staged messages + indicador
|
// Verificar si el usuario está cerca del final del chat
|
||||||
// Esto evita el error que borra la pantalla al intentar recargar automáticamente
|
const container = document.getElementById('chat-conversations');
|
||||||
|
const nearBottom = container ? ((container.scrollHeight - container.scrollTop - container.clientHeight) < 150) : true;
|
||||||
|
|
||||||
|
if (nearBottom && !this._userScrolling) {
|
||||||
|
// Usuario está en el final: recargar mensajes automáticamente
|
||||||
|
console.log('🔄 Usuario cerca del final, recargando mensajes...');
|
||||||
|
this.loadMessages(this.currentUserId, true, true).catch(err => {
|
||||||
|
console.error('Error recargando mensajes:', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Usuario está leyendo historial: usar staged messages
|
||||||
|
console.log('📜 Usuario leyendo historial, guardando en staging...');
|
||||||
if (!this._stagedMessages) this._stagedMessages = [];
|
if (!this._stagedMessages) this._stagedMessages = [];
|
||||||
if (!this._stagedMessageIds) this._stagedMessageIds = new Set();
|
if (!this._stagedMessageIds) this._stagedMessageIds = new Set();
|
||||||
|
|
||||||
@@ -1236,6 +1241,7 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
} else {
|
} else {
|
||||||
console.log('⏭️ Mensaje duplicado, omitiendo');
|
console.log('⏭️ Mensaje duplicado, omitiendo');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('📋 Mensaje para OTRA conversación, solo actualizando lista');
|
console.log('📋 Mensaje para OTRA conversación, solo actualizando lista');
|
||||||
}
|
}
|
||||||
@@ -1380,7 +1386,7 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
*/
|
*/
|
||||||
updateConversationInList(data) {
|
updateConversationInList(data) {
|
||||||
try {
|
try {
|
||||||
console.log('📝 Actualizando conversación en lista:', data);
|
console.log('📝 updateConversationInList INICIADO:', data);
|
||||||
|
|
||||||
// Asegurar que conversations esté inicializado
|
// Asegurar que conversations esté inicializado
|
||||||
if (!this.conversations || !Array.isArray(this.conversations)) {
|
if (!this.conversations || !Array.isArray(this.conversations)) {
|
||||||
@@ -1396,6 +1402,8 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('👤 User ID extraído:', userId);
|
||||||
|
|
||||||
// Si no viene el mensaje, recargar la lista completa para obtener datos actualizados
|
// Si no viene el mensaje, recargar la lista completa para obtener datos actualizados
|
||||||
if (!data.message && !data.content && !data.text && !data.last_message) {
|
if (!data.message && !data.content && !data.text && !data.last_message) {
|
||||||
console.log('⚠️ Evento SSE sin contenido de mensaje, recargando lista completa...');
|
console.log('⚠️ Evento SSE sin contenido de mensaje, recargando lista completa...');
|
||||||
@@ -1412,11 +1420,14 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
const message = data.message || data.content || data.text || data.last_message || 'Nuevo mensaje';
|
const message = data.message || data.content || data.text || data.last_message || 'Nuevo mensaje';
|
||||||
const timestamp = data.timestamp || data.created_at || new Date().toISOString();
|
const timestamp = data.timestamp || data.created_at || new Date().toISOString();
|
||||||
|
|
||||||
|
console.log('💬 Mensaje extraído:', message);
|
||||||
|
console.log('⏰ Timestamp:', timestamp);
|
||||||
|
|
||||||
// Buscar la conversación en el array
|
// Buscar la conversación en el array
|
||||||
const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(userId));
|
const existingIndex = this.conversations.findIndex(c => String(c.user_id) === String(userId));
|
||||||
|
|
||||||
if (existingIndex !== -1) {
|
if (existingIndex !== -1) {
|
||||||
console.log('✅ Conversación encontrada, actualizando...');
|
console.log('✅ Conversación encontrada en índice:', existingIndex);
|
||||||
// Actualizar conversación existente
|
// Actualizar conversación existente
|
||||||
const conv = this.conversations[existingIndex];
|
const conv = this.conversations[existingIndex];
|
||||||
conv.last_message = message;
|
conv.last_message = message;
|
||||||
@@ -1425,14 +1436,17 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
// Solo incrementar unread_count si no es la conversación activa
|
// Solo incrementar unread_count si no es la conversación activa
|
||||||
if (String(this.currentUserId) !== String(userId)) {
|
if (String(this.currentUserId) !== String(userId)) {
|
||||||
conv.unread_count = (conv.unread_count || 0) + 1;
|
conv.unread_count = (conv.unread_count || 0) + 1;
|
||||||
|
console.log('📬 unread_count incrementado a:', conv.unread_count);
|
||||||
} else {
|
} else {
|
||||||
// Si es la conversación activa, mantener unread_count en 0
|
// Si es la conversación activa, mantener unread_count en 0
|
||||||
conv.unread_count = 0;
|
conv.unread_count = 0;
|
||||||
|
console.log('✅ Conversación activa, unread_count = 0');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mover al inicio de la lista
|
// Mover al inicio de la lista
|
||||||
this.conversations.splice(existingIndex, 1);
|
this.conversations.splice(existingIndex, 1);
|
||||||
this.conversations.unshift(conv);
|
this.conversations.unshift(conv);
|
||||||
|
console.log('⬆️ Conversación movida al inicio');
|
||||||
} else {
|
} else {
|
||||||
console.log('➕ Conversación no existe, agregando nueva...');
|
console.log('➕ Conversación no existe, agregando nueva...');
|
||||||
// Nueva conversación, agregar al inicio
|
// Nueva conversación, agregar al inicio
|
||||||
@@ -1444,22 +1458,25 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
last_time: timestamp,
|
last_time: timestamp,
|
||||||
unread_count: String(this.currentUserId) !== String(userId) ? 1 : 0
|
unread_count: String(this.currentUserId) !== String(userId) ? 1 : 0
|
||||||
});
|
});
|
||||||
|
console.log('✅ Nueva conversación agregada');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-renderizar solo la lista de conversaciones
|
// Re-renderizar solo la lista de conversaciones
|
||||||
console.log('🔄 Re-renderizando lista de conversaciones...');
|
console.log('🔄 Re-renderizando lista de conversaciones...');
|
||||||
|
console.log('📊 Total conversaciones:', this.conversations.length);
|
||||||
console.log('🔍 Filtro activo:', this.conversationFilter);
|
console.log('🔍 Filtro activo:', this.conversationFilter);
|
||||||
console.log('🔍 this.conversations:', this.conversations);
|
|
||||||
|
|
||||||
if (typeof this.renderConversations === 'function') {
|
if (typeof this.renderConversations === 'function') {
|
||||||
console.log('✅ Llamando a renderConversations()...');
|
console.log('✅ Llamando a renderConversations()...');
|
||||||
this.renderConversations();
|
this.renderConversations();
|
||||||
|
console.log('✅ renderConversations() ejecutado correctamente');
|
||||||
} else {
|
} else {
|
||||||
console.error('❌ renderConversations no es una función!');
|
console.error('❌ renderConversations no es una función!');
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error actualizando conversación en lista:', error);
|
console.error('❌ Error updateConversationInList:', error);
|
||||||
|
console.error('Stack trace:', error.stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2110,49 +2127,6 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
};
|
};
|
||||||
applyInitialMobile();
|
applyInitialMobile();
|
||||||
window.addEventListener('resize', applyInitialMobile);
|
window.addEventListener('resize', applyInitialMobile);
|
||||||
|
|
||||||
// PWA support (install prompt + SW)
|
|
||||||
let _deferredPWA = null;
|
|
||||||
const _pwaBtnConv = document.getElementById('pwa-install-btn');
|
|
||||||
window.addEventListener('beforeinstallprompt', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
_deferredPWA = e;
|
|
||||||
if (_pwaBtnConv) { _pwaBtnConv.style.display = 'inline-block'; _pwaBtnConv.classList.remove('d-none'); }
|
|
||||||
});
|
|
||||||
if (_pwaBtnConv) {
|
|
||||||
_pwaBtnConv.addEventListener('click', async () => {
|
|
||||||
if (!_deferredPWA) return;
|
|
||||||
_deferredPWA.prompt();
|
|
||||||
const choice = await _deferredPWA.userChoice;
|
|
||||||
console.debug('PWA install (conv):', choice);
|
|
||||||
if (choice && choice.outcome === 'accepted') _pwaBtnConv.style.display = 'none';
|
|
||||||
_deferredPWA = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
window.addEventListener('appinstalled', () => console.debug('PWA installed (conversations)'));
|
|
||||||
if ('serviceWorker' in navigator) {
|
|
||||||
(async () => {
|
|
||||||
try {
|
|
||||||
// Check if the service-worker script is actually reachable to avoid noisy 404 warnings
|
|
||||||
const resp = await fetch('/service-worker.js', { method: 'GET', cache: 'no-store' });
|
|
||||||
if (resp && resp.ok) {
|
|
||||||
navigator.serviceWorker.register('/service-worker.js')
|
|
||||||
.then(() => console.debug('SW reg (conv)'))
|
|
||||||
.catch(e => console.warn('SW reg failed', e));
|
|
||||||
} else {
|
|
||||||
console.debug('Service worker not registered: script not found (status ' + (resp && resp.status) + ')');
|
|
||||||
// Remove manifest link if it's missing to avoid extra 404s
|
|
||||||
try {
|
|
||||||
const link = document.querySelector('link[rel="manifest"]');
|
|
||||||
if (link && (!resp || resp.status === 404)) link.parentNode && link.parentNode.removeChild(link);
|
|
||||||
} catch(e) {}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// Network or fetch failed — don't spam console with errors
|
|
||||||
console.debug('Service worker check failed:', e);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopRecBtn = document.getElementById('stop-recording');
|
const stopRecBtn = document.getElementById('stop-recording');
|
||||||
@@ -2524,7 +2498,9 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
|
|
||||||
|
|
||||||
renderConversations() {
|
renderConversations() {
|
||||||
console.log('🎨 Renderizando conversaciones:', this.conversations.length);
|
console.log('🎨 renderConversations INICIADO');
|
||||||
|
console.log('📊 Total conversaciones:', this.conversations?.length || 0);
|
||||||
|
|
||||||
const container = document.getElementById('conversation-list');
|
const container = document.getElementById('conversation-list');
|
||||||
|
|
||||||
if (!container) {
|
if (!container) {
|
||||||
@@ -2631,13 +2607,21 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
const wasNearTop = prevScrollTop < 60;
|
const wasNearTop = prevScrollTop < 60;
|
||||||
|
|
||||||
console.log('🔄 Actualizando innerHTML del container...');
|
console.log('🔄 Actualizando innerHTML del container...');
|
||||||
|
console.log('📏 Scroll antes - Top:', prevScrollTop, 'Height:', prevScrollHeight);
|
||||||
|
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
console.log('✅ DOM actualizado con', this.conversations.length, 'conversaciones');
|
|
||||||
|
console.log('✅ DOM actualizado');
|
||||||
console.log('📊 Elementos en DOM:', container.children.length);
|
console.log('📊 Elementos en DOM:', container.children.length);
|
||||||
|
|
||||||
|
// Forzar repaint del DOM
|
||||||
|
void container.offsetHeight;
|
||||||
|
|
||||||
const newScrollHeight = container.scrollHeight;
|
const newScrollHeight = container.scrollHeight;
|
||||||
const scrollDelta = newScrollHeight - prevScrollHeight;
|
const scrollDelta = newScrollHeight - prevScrollHeight;
|
||||||
|
|
||||||
|
console.log('📏 Scroll después - Height:', newScrollHeight, 'Delta:', scrollDelta);
|
||||||
|
|
||||||
if (wasNearTop) {
|
if (wasNearTop) {
|
||||||
// keep at top
|
// keep at top
|
||||||
container.scrollTop = 0;
|
container.scrollTop = 0;
|
||||||
@@ -2645,10 +2629,15 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
// preserve visual offset (avoid jumping) whether the user is scrolling or not
|
// preserve visual offset (avoid jumping) whether the user is scrolling or not
|
||||||
container.scrollTop = Math.max(0, prevScrollTop + scrollDelta);
|
container.scrollTop = Math.max(0, prevScrollTop + scrollDelta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('✅ renderConversations COMPLETADO');
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// fallback to naive replace if anything failed
|
// fallback to naive replace if anything failed
|
||||||
console.warn('renderConversations: scroll preservation failed', e);
|
console.warn('renderConversations: scroll preservation failed', e);
|
||||||
container.innerHTML = html;
|
container.innerHTML = html;
|
||||||
|
// Forzar repaint
|
||||||
|
void container.offsetHeight;
|
||||||
|
console.log('⚠️ renderConversations COMPLETADO con fallback');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4046,9 +4035,40 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
|
|
||||||
this.startRecording = async function() {
|
this.startRecording = async function() {
|
||||||
try {
|
try {
|
||||||
|
// Verificar que el navegador soporta MediaDevices
|
||||||
|
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||||
|
throw new Error('Tu navegador no soporta grabación de audio');
|
||||||
|
}
|
||||||
|
|
||||||
// Suspend periodic reload while recording
|
// Suspend periodic reload while recording
|
||||||
this._suspendAutoRefresh = true;
|
this._suspendAutoRefresh = true;
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
|
||||||
|
// Intentar obtener acceso al micrófono con diferentes configuraciones
|
||||||
|
let stream = null;
|
||||||
|
try {
|
||||||
|
// Intento 1: Configuración básica sin especificar dispositivo
|
||||||
|
stream = await navigator.mediaDevices.getUserMedia({
|
||||||
|
audio: {
|
||||||
|
echoCancellation: true,
|
||||||
|
noiseSuppression: true,
|
||||||
|
autoGainControl: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (err1) {
|
||||||
|
console.warn('Intento 1 falló, probando configuración más simple:', err1.message);
|
||||||
|
try {
|
||||||
|
// Intento 2: Configuración minimalista
|
||||||
|
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
||||||
|
} catch (err2) {
|
||||||
|
console.error('Todos los intentos fallaron');
|
||||||
|
throw err2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!stream) {
|
||||||
|
throw new Error('No se pudo obtener acceso al micrófono');
|
||||||
|
}
|
||||||
|
|
||||||
this._mediaRecorder = new MediaRecorder(stream);
|
this._mediaRecorder = new MediaRecorder(stream);
|
||||||
const chunks = [];
|
const chunks = [];
|
||||||
this._mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
|
this._mediaRecorder.ondataavailable = (e) => { if (e.data && e.data.size) chunks.push(e.data); };
|
||||||
@@ -4114,8 +4134,29 @@ if (!isset($_SESSION['user_id'])) {
|
|||||||
update();
|
update();
|
||||||
this._recordingInterval = setInterval(update, 1000);
|
this._recordingInterval = setInterval(update, 1000);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('startRecording error', err);
|
console.error('startRecording error', err.name, err.message);
|
||||||
alert('No se pudo acceder al micrófono: ' + (err.message||err));
|
|
||||||
|
// Mensajes de error más específicos
|
||||||
|
let errorMsg = 'No se pudo acceder al micrófono';
|
||||||
|
|
||||||
|
if (err.name === 'NotFoundError') {
|
||||||
|
errorMsg = 'No se encontró ningún micrófono. Conecta un micrófono e intenta de nuevo.';
|
||||||
|
} else if (err.name === 'NotAllowedError' || err.name === 'PermissionDeniedError') {
|
||||||
|
errorMsg = 'Permiso denegado. Permite el acceso al micrófono en la configuración del navegador.';
|
||||||
|
} else if (err.name === 'NotReadableError') {
|
||||||
|
errorMsg = 'El micrófono está siendo usado por otra aplicación.';
|
||||||
|
} else if (err.message) {
|
||||||
|
errorMsg += ': ' + err.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
alert(errorMsg);
|
||||||
|
|
||||||
|
// Limpiar estado
|
||||||
|
this._suspendAutoRefresh = false;
|
||||||
|
const ind = document.getElementById('recording-indicator');
|
||||||
|
if (ind) ind.style.display = 'none';
|
||||||
|
const micEl = document.getElementById('mic-btn');
|
||||||
|
if (micEl) micEl.classList.remove('recording', 'recording-cancel');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ server {
|
|||||||
root /var/www/html;
|
root /var/www/html;
|
||||||
index index.php index.html;
|
index index.php index.html;
|
||||||
|
|
||||||
|
# 🚫 CACHE DESACTIVADO GLOBALMENTE
|
||||||
|
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0" always;
|
||||||
|
add_header Pragma "no-cache" always;
|
||||||
|
add_header Expires "0" always;
|
||||||
|
|
||||||
# Security headers
|
# Security headers
|
||||||
add_header X-Frame-Options "SAMEORIGIN" always;
|
add_header X-Frame-Options "SAMEORIGIN" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
@@ -79,10 +84,12 @@ server {
|
|||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Static assets caching
|
# Static assets - SIN CACHE para desarrollo
|
||||||
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
|
||||||
expires 30d;
|
# CACHE DESACTIVADO
|
||||||
add_header Cache-Control "public, immutable";
|
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
|
||||||
|
add_header Pragma "no-cache" always;
|
||||||
|
expires off;
|
||||||
access_log off;
|
access_log off;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,10 +112,12 @@ server {
|
|||||||
alias /var/www/html/uploads/;
|
alias /var/www/html/uploads/;
|
||||||
autoindex off;
|
autoindex off;
|
||||||
|
|
||||||
# Security: solo permitir ciertos tipos de archivo
|
# Security: solo permitir ciertos tipos de archivo - SIN CACHE
|
||||||
location ~* \.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|mp4|mp3|webp)$ {
|
location ~* \.(jpg|jpeg|png|gif|pdf|doc|docx|xls|xlsx|mp4|mp3|webp)$ {
|
||||||
expires 7d;
|
# CACHE DESACTIVADO
|
||||||
add_header Cache-Control "public";
|
add_header Cache-Control "no-store, no-cache, must-revalidate, max-age=0" always;
|
||||||
|
add_header Pragma "no-cache" always;
|
||||||
|
expires off;
|
||||||
}
|
}
|
||||||
|
|
||||||
# Denegar ejecución de PHP en uploads
|
# Denegar ejecución de PHP en uploads
|
||||||
|
|||||||
@@ -51,9 +51,9 @@ http {
|
|||||||
client_header_timeout 12;
|
client_header_timeout 12;
|
||||||
send_timeout 10;
|
send_timeout 10;
|
||||||
|
|
||||||
# FastCGI cache (opcional)
|
# FastCGI cache DESACTIVADO para desarrollo
|
||||||
fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=whatsapp_cache:10m
|
# fastcgi_cache_path /var/cache/nginx levels=1:2 keys_zone=whatsapp_cache:10m
|
||||||
max_size=100m inactive=60m use_temp_path=off;
|
# max_size=100m inactive=60m use_temp_path=off;
|
||||||
|
|
||||||
# Include virtual hosts
|
# Include virtual hosts
|
||||||
include /etc/nginx/http.d/*.conf;
|
include /etc/nginx/http.d/*.conf;
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
# Configuración SSL para bot.u-s.app
|
# Configuración SSL para bot.u-s.app
|
||||||
server {
|
server {
|
||||||
listen 443 ssl http2;
|
listen 443 ssl;
|
||||||
listen [::]:443 ssl http2;
|
listen [::]:443 ssl;
|
||||||
|
http2 on;
|
||||||
server_name bot.u-s.app localhost;
|
server_name bot.u-s.app localhost;
|
||||||
root /var/www/html;
|
root /var/www/html;
|
||||||
index index.php index.html;
|
index index.php index.html;
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "WhatsApp Bot Manager",
|
|
||||||
"short_name": "WA Bot",
|
|
||||||
"start_url": "/",
|
|
||||||
"display": "standalone",
|
|
||||||
"background_color": "#ffffff",
|
|
||||||
"theme_color": "#10b981",
|
|
||||||
"icons": [
|
|
||||||
{
|
|
||||||
"src": "/assets/icons/icon-192.png",
|
|
||||||
"sizes": "192x192",
|
|
||||||
"type": "image/png"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
// Service worker - Force no-cache for API requests
|
|
||||||
const CACHE_VERSION = 'v1.0.1';
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
|
||||||
// activate immediately
|
|
||||||
self.skipWaiting();
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('activate', (event) => {
|
|
||||||
event.waitUntil(
|
|
||||||
caches.keys().then(keys => {
|
|
||||||
// Clear all caches on activation
|
|
||||||
return Promise.all(keys.map(key => caches.delete(key)));
|
|
||||||
}).then(() => self.clients.claim())
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
|
||||||
const url = new URL(event.request.url);
|
|
||||||
|
|
||||||
// For API requests, always go to network with no-cache
|
|
||||||
if (url.pathname.startsWith('/api/') || url.pathname.includes('/api/')) {
|
|
||||||
event.respondWith(
|
|
||||||
fetch(event.request, {
|
|
||||||
cache: 'no-store',
|
|
||||||
headers: {
|
|
||||||
...Object.fromEntries(event.request.headers),
|
|
||||||
'Cache-Control': 'no-cache, no-store, must-revalidate',
|
|
||||||
'Pragma': 'no-cache'
|
|
||||||
}
|
|
||||||
}).catch(err => {
|
|
||||||
console.error('SW fetch error:', err);
|
|
||||||
return new Response(JSON.stringify({ error: 'Network error' }), {
|
|
||||||
status: 503,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// For other requests, use default network behavior (no caching)
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user