2929 <link rel="stylesheet" href="css/mobile-network-switcher-fix.css">
3030 <link rel="stylesheet" href="css/mobile-wallet-connect-fix.css">
3131 <link rel="stylesheet" href="css/transaction-modal.css">
32- <link rel="stylesheet" href="css/groups-page.css?v=2.9 ">
32+ <link rel="stylesheet" href="css/groups-page.css?v=3.0 ">
3333 <link rel="stylesheet" href="shared-components.css">
3434 <link rel="stylesheet" href="notification-system.css">
3535 <link rel="stylesheet" href="loading-styles.css">
@@ -292,6 +292,31 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
292292 refreshTandas();
293293 }
294294
295+ // Non-blocking fetch of public groups for discovery
296+ (async function() {
297+ try {
298+ var pubRes = await fetch(API_BASE + '/api/groups/public-pg', {
299+ headers: { 'Authorization': 'Bearer ' + authToken }
300+ });
301+ var pubData = await pubRes.json();
302+ if (pubData.success) {
303+ window.publicGroupsData = (pubData.data.groups || []).map(function(g) {
304+ var adapted = adaptPostgreSQLGroup(g);
305+ adapted.my_role = 'open';
306+ adapted.my_payment_status = null;
307+ adapted.my_alerts = [];
308+ adapted.is_public = true;
309+ adapted.admin_name = g.admin_name || null;
310+ return adapted;
311+ });
312+ } else {
313+ window.publicGroupsData = [];
314+ }
315+ } catch(e) {
316+ window.publicGroupsData = [];
317+ }
318+ })();
319+
295320 // ✅ Check if there's a newly created group to highlight
296321 const newlyCreatedId = sessionStorage.getItem('newly_created_group_id');
297322 if (newlyCreatedId) {
@@ -496,7 +521,7 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
496521 function renderGroups() {
497522 const container = document.getElementById('groupsContainer');
498523
499- if (allGroups.length === 0) {
524+ if (allGroups.length === 0 && (!window.publicGroupsData || window.publicGroupsData.length === 0) ) {
500525 container.innerHTML = '<div class="empty-state">' +
501526 '<div class="empty-state-icon"><i class="fas fa-users" style="font-size:3rem;color:#00FFFF;"></i></div>' +
502527 '<h3>Comienza tu primera tanda</h3>' +
@@ -518,13 +543,22 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
518543 return;
519544 }
520545
521- const html = `
522- <div class="groups-grid">
523- ${filteredGroups.map(group => renderGroupCard(group)).join('')}
524- </div>
525- `;
546+ var myCards = '';
547+ var pubCards = '';
548+ filteredGroups.forEach(function(group) {
549+ if (group.is_public) {
550+ pubCards += renderGroupCard(group);
551+ } else {
552+ myCards += renderGroupCard(group);
553+ }
554+ });
555+ var finalHtml = '<div class="groups-grid">' + myCards;
556+ if (pubCards) {
557+ finalHtml += '</div><div class="gc-section-divider"><span>Grupos Abiertos</span></div><div class="groups-grid">' + pubCards;
558+ }
559+ finalHtml += '</div>';
526560
527- container.innerHTML = html ;
561+ container.innerHTML = finalHtml ;
528562
529563 // Dispatch event for position requests to load
530564 setTimeout(() => {
@@ -672,7 +706,7 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
672706 'weekly': 'Semanal', 'biweekly': 'Quincenal', 'monthly': 'Mensual'
673707 };
674708 const roleLabels = {
675- 'creator': 'Creador', 'coordinator': 'Coordinador', 'member': 'Miembro'
709+ 'creator': 'Creador', 'coordinator': 'Coordinador', 'member': 'Miembro', 'open': 'Abierto'
676710 };
677711 const alertIcons = {
678712 'success': '✓', 'warning': '⚠', 'danger': '✗', 'info': 'ℹ'
@@ -749,6 +783,18 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
749783 actionsHtml += '<button class="btn pr-request-btn" data-action="grp-request-extension" data-group-id="' + escapeHtml(group.id) + '">Solicitar Prorroga</button>';
750784 }
751785
786+ // Public group card overrides (discover open groups)
787+ if (group.is_public) {
788+ statusColor = '#f59e0b';
789+ roleClass = 'gc-role-open';
790+ if (group.admin_name) {
791+ var adminPart = 'Creado por: ' + escapeHtml(group.admin_name);
792+ subtitle = subtitle ? subtitle + ' · ' + adminPart : adminPart;
793+ }
794+ alertsHtml = '';
795+ actionsHtml = '<button class="btn gc-btn-join" data-action="grp-join-request" data-group-id="' + escapeHtml(group.id) + '" data-group-name="' + escapeHtml(group.name) + '">Solicitar unirse</button>';
796+ }
797+
752798 return '<div class="group-card" data-group-id="' + escapeHtml(group.id) + '" data-status="' + escapeHtml(st) + '" style="border-color:' + statusColor + '">' +
753799 '<div class="gc-header">' +
754800 '<div class="gc-avatar" style="background:' + statusColor + ';color:' + (st === 'suspended' || st === 'cancelled' ? '#fff' : '#0f172a') + '">' + avatarLetter + '</div>' +
@@ -796,6 +842,16 @@ <h1 class="main-feed-title" style="font-size:1.5rem;font-weight:700;">
796842 return matchesRole && matchesPayment && matchesSearch;
797843 });
798844
845+ // Append public groups only when both filters are "Todos"
846+ if (roleFilter === 'all' && paymentFilter === 'all') {
847+ var pubGroups = (window.publicGroupsData || []).filter(function(g) {
848+ return !searchTerm || (g.name && g.name.toLowerCase().includes(searchTerm));
849+ });
850+ if (pubGroups.length > 0) {
851+ filteredGroups = filteredGroups.concat(pubGroups);
852+ }
853+ }
854+
799855 renderGroups();
800856
801857 // Also refresh tandas tab to sync turn positions
@@ -3667,10 +3723,10 @@ <h4 class="grp-sidebar-stats-title">Guia</h4>
36673723 <h3 style="color: #f8fafc; margin-bottom: 12px;">Aún no perteneces a ningún grupo</h3>
36683724 <p style="color: #9ca3af; margin-bottom: 24px;">Únete a un grupo existente o crea uno nuevo para comenzar tu primera tanda.</p>
36693725 <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
3670- <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 12px 24px ;">
3726+ <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 8px 18px; font-size: 0.85rem ;">
36713727 <i class="fas fa-search"></i> Explorar Grupos
36723728 </button>
3673- <button class="btn btn-outline" data-action="grp-switch-tab" data-tab="create" style="padding: 12px 24px ;">
3729+ <button class="btn btn-outline" data-action="grp-switch-tab" data-tab="create" style="padding: 8px 18px; font-size: 0.85rem ;">
36743730 <i class="fas fa-plus"></i> Crear Grupo
36753731 </button>
36763732 </div>
@@ -3683,7 +3739,7 @@ <h3 style="color: #f8fafc; margin-bottom: 12px;">Aún no perteneces a ningún gr
36833739 <h3 style="color: #f8fafc; margin-bottom: 12px;">Tus grupos aún no tienen tandas activas</h3>
36843740 <p style="color: #9ca3af; margin-bottom: 24px;">Estás en ${groupCount || 1} grupo(s). El coordinador debe activar la tanda para comenzar.</p>
36853741 <div style="display: flex; gap: 12px; justify-content: center; flex-wrap: wrap;">
3686- <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="my-groups" style="padding: 12px 24px ;">
3742+ <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="my-groups" style="padding: 8px 18px; font-size: 0.85rem ;">
36873743 <i class="fas fa-users"></i> Ver Mis Grupos
36883744 </button>
36893745 </div>
@@ -3696,7 +3752,7 @@ <h3 style="color: #f8fafc; margin-bottom: 12px;">Tus grupos aún no tienen tanda
36963752 <div class="empty-icon" style="font-size: 4rem; margin-bottom: 20px;">💰</div>
36973753 <h3 style="color: #f8fafc; margin-bottom: 12px;">No hay tandas</h3>
36983754 <p style="color: #9ca3af; margin-bottom: 24px;">${escapeHtml(stateOrMessage)}</p>
3699- <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 12px 24px ;">
3755+ <button class="btn btn-primary" data-action="grp-switch-tab" data-tab="groups" style="padding: 8px 18px; font-size: 0.85rem ;">
37003756 <i class="fas fa-search"></i> Explorar Grupos
37013757 </button>
37023758 </div>
@@ -11904,6 +11960,48 @@ <h4><i class="fas fa-list-ol"></i> Orden de Turnos</h4>
1190411960 break;
1190511961
1190611962 // Filter & Retry
11963+ case 'grp-join-request':
11964+ (function() {
11965+ var joinBtn = btn;
11966+ var joinGid = joinBtn.getAttribute('data-group-id');
11967+ var joinName = joinBtn.getAttribute('data-group-name') || 'este grupo';
11968+ if (!joinGid) return;
11969+ showConfirm('Deseas solicitar unirte a ' + escapeHtml(joinName) + '?', function() {
11970+ joinBtn.disabled = true;
11971+ joinBtn.textContent = 'Enviando...';
11972+ var authTk = localStorage.getItem('auth_token') || sessionStorage.getItem('auth_token') || '';
11973+ fetch(API_BASE + '/api/groups/' + encodeURIComponent(joinGid) + '/join-pg', {
11974+ method: 'POST',
11975+ headers: {
11976+ 'Content-Type': 'application/json',
11977+ 'Authorization': 'Bearer ' + authTk
11978+ },
11979+ body: JSON.stringify({})
11980+ })
11981+ .then(function(r) { return r.json(); })
11982+ .then(function(result) {
11983+ if (result.success) {
11984+ var msg = result.data && result.data.auto_approved
11985+ ? 'Te has unido a ' + escapeHtml(joinName)
11986+ : 'Solicitud enviada a ' + escapeHtml(joinName) + '. El administrador la revisara';
11987+ showNotification(msg, 'success');
11988+ // Remove from public list
11989+ window.publicGroupsData = (window.publicGroupsData || []).filter(function(g) { return g.id !== joinGid; });
11990+ fetchMyGroups();
11991+ } else {
11992+ showNotification(result.error || 'Error al solicitar ingreso', 'error');
11993+ joinBtn.disabled = false;
11994+ joinBtn.textContent = 'Solicitar unirse';
11995+ }
11996+ })
11997+ .catch(function() {
11998+ showNotification('Error de conexion', 'error');
11999+ joinBtn.disabled = false;
12000+ joinBtn.textContent = 'Solicitar unirse';
12001+ });
12002+ });
12003+ })();
12004+ break;
1190712005 case 'grp-reset-filters':
1190812006 if (typeof resetFilters === 'function') resetFilters();
1190912007 break;
0 commit comments