up
This commit is contained in:
@@ -197,10 +197,10 @@ function notifConfig() {
|
||||
toast: { visible: false, ok: true, msg: '' },
|
||||
|
||||
async init() {
|
||||
// Verificar si hay telegram configurado
|
||||
// Verificar si hay telegram configurado (la API devuelve array directo)
|
||||
try {
|
||||
const t = await axios.get('/app/loadtelegram');
|
||||
this.hasTelegramConfig = (t.data?.items?.length ?? 0) > 0;
|
||||
this.hasTelegramConfig = Array.isArray(t.data) && t.data.some(b => b.activo);
|
||||
} catch {}
|
||||
|
||||
// Cargar configuración existente
|
||||
|
||||
@@ -72,8 +72,14 @@
|
||||
<div x-show="tab === 'usuarios'">
|
||||
<div x-show="items.length === 0 && !loading" class="text-sm text-gray-400 py-8 text-center">Sin datos. Haz clic en "Cargar".</div>
|
||||
|
||||
<!-- Toolbar: ordenar por vencimiento -->
|
||||
<!-- Toolbar: filtro plan + ordenar por vencimiento -->
|
||||
<div class="flex flex-wrap items-center gap-3 mb-3" x-show="items.length > 0">
|
||||
<select x-model="filterPlan" class="border border-gray-300 rounded-lg px-3 py-1.5 text-xs focus:outline-none focus:ring-1 focus:ring-[#8eb02f]">
|
||||
<option value="">— Todos los planes —</option>
|
||||
<template x-for="p in planesDisponibles()" :key="p">
|
||||
<option :value="p" x-text="p"></option>
|
||||
</template>
|
||||
</select>
|
||||
<button @click="toggleSortVenc()" :disabled="loadingVenc"
|
||||
:class="sortByVenc ? 'bg-[#8eb02f] text-white border-[#8eb02f]' : 'text-gray-600 border-gray-300 hover:bg-gray-50'"
|
||||
class="flex items-center gap-1.5 text-xs px-3 py-1.5 border rounded-lg transition disabled:opacity-50">
|
||||
@@ -106,7 +112,7 @@
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template x-for="u in items" :key="u.id || u._id">
|
||||
<template x-for="u in usuariosFiltrados()" :key="u.id || u._id">
|
||||
<tr :class="sortByVenc ? vencimientoRowClass(u) : 'hover:bg-gray-50'" class="border-b border-gray-100 transition-colors">
|
||||
<td class="py-2 px-3 font-medium" x-text="u.name || u.nombre || '—'"></td>
|
||||
<td class="py-2 px-3 text-gray-500 text-xs" x-text="u.email || '—'"></td>
|
||||
@@ -1574,6 +1580,7 @@ function vcardApiApp() {
|
||||
// Ordenar usuarios por vencimiento
|
||||
sortByVenc: false,
|
||||
loadingVenc: false,
|
||||
filterPlan: '',
|
||||
membresiaMap: {}, // { userId: { fecha_fin, activo, plan } }
|
||||
|
||||
// Desactivar usuario + VCards
|
||||
@@ -1811,15 +1818,7 @@ function vcardApiApp() {
|
||||
|
||||
// ─── Ordenar por vencimiento ─────────────────────────────────────────
|
||||
|
||||
// Retorna true para planes de pago (emprendimiento / normal).
|
||||
// Estos planes no tienen vencimiento relevante en el contexto de membresía premium.
|
||||
_isPaidPlan(u) {
|
||||
const nombre = ((u.plan && u.plan.nombre) || '').toLowerCase();
|
||||
return nombre.includes('emprendimiento') || nombre.includes('normal');
|
||||
},
|
||||
|
||||
diasHastaVencer(u) {
|
||||
if (this._isPaidPlan(u)) return null; // plan de pago: sin vencimiento
|
||||
const id = u.id || u._id;
|
||||
const m = this.membresiaMap[id];
|
||||
if (!m || !m.fecha_fin) return null;
|
||||
@@ -1845,7 +1844,6 @@ function vcardApiApp() {
|
||||
},
|
||||
|
||||
vencimientoTexto(u) {
|
||||
if (this._isPaidPlan(u)) return '—'; // plan de pago: no aplica vencimiento
|
||||
const id = u.id || u._id;
|
||||
const m = this.membresiaMap[id];
|
||||
if (this.loadingVenc && !m) return '...';
|
||||
@@ -1860,10 +1858,8 @@ function vcardApiApp() {
|
||||
async _fetchAndSortVenc() {
|
||||
this.loadingVenc = true;
|
||||
const batchSize = 5;
|
||||
// Solo buscar membresía para usuarios free (los de plan de pago no tienen vencimiento relevante)
|
||||
const usersToFetch = this.items.filter(u => !this._isPaidPlan(u));
|
||||
for (let i = 0; i < usersToFetch.length; i += batchSize) {
|
||||
const batch = usersToFetch.slice(i, i + batchSize);
|
||||
for (let i = 0; i < this.items.length; i += batchSize) {
|
||||
const batch = this.items.slice(i, i + batchSize);
|
||||
await Promise.all(batch.map(async u => {
|
||||
const id = u.id || u._id;
|
||||
if (this.membresiaMap[id]) return;
|
||||
@@ -1874,32 +1870,15 @@ function vcardApiApp() {
|
||||
}));
|
||||
}
|
||||
this.loadingVenc = false;
|
||||
// Orden: 0 = free+premium (más urgente, por fecha), 1 = plan pagado, 2 = free sin premium
|
||||
this.items = [...this.items].sort((a, b) => {
|
||||
const paidA = this._isPaidPlan(a);
|
||||
const paidB = this._isPaidPlan(b);
|
||||
const premA = a.premium === true || a.premium === 'true';
|
||||
const premB = b.premium === true || b.premium === 'true';
|
||||
const grp = (isPaid, isPrem) => {
|
||||
if (!isPaid && isPrem) return 0; // free + premium → primero
|
||||
if (isPaid) return 1; // plan pagado → en medio
|
||||
return 2; // free sin premium → al final
|
||||
};
|
||||
const ga = grp(paidA, premA);
|
||||
const gb = grp(paidB, premB);
|
||||
if (ga !== gb) return ga - gb;
|
||||
// Dentro del grupo 0 (free+premium), ordenar por fecha_fin (más próxima primero)
|
||||
if (ga === 0) {
|
||||
const ma = this.membresiaMap[a.id || a._id];
|
||||
const mb = this.membresiaMap[b.id || b._id];
|
||||
const fa = ma?.fecha_fin ? new Date(ma.fecha_fin) : null;
|
||||
const fb = mb?.fecha_fin ? new Date(mb.fecha_fin) : null;
|
||||
if (!fa && !fb) return 0;
|
||||
if (!fa) return 1;
|
||||
if (!fb) return -1;
|
||||
return fa - fb;
|
||||
}
|
||||
return 0;
|
||||
const ma = this.membresiaMap[a.id || a._id];
|
||||
const mb = this.membresiaMap[b.id || b._id];
|
||||
const fa = ma?.fecha_fin ? new Date(ma.fecha_fin) : null;
|
||||
const fb = mb?.fecha_fin ? new Date(mb.fecha_fin) : null;
|
||||
if (!fa && !fb) return 0;
|
||||
if (!fa) return 1;
|
||||
if (!fb) return -1;
|
||||
return fa - fb;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1926,6 +1905,16 @@ function vcardApiApp() {
|
||||
return montos.slice(0, 5); // max 5 precios únicos
|
||||
},
|
||||
|
||||
planesDisponibles() {
|
||||
const nombres = [...new Set(this.items.map(u => (u.plan && u.plan.nombre) || ''))].filter(Boolean).sort();
|
||||
return nombres;
|
||||
},
|
||||
|
||||
usuariosFiltrados() {
|
||||
if (!this.filterPlan) return this.items;
|
||||
return this.items.filter(u => ((u.plan && u.plan.nombre) || '') === this.filterPlan);
|
||||
},
|
||||
|
||||
// ─── Planes ──────────────────────────────────────────────────────────
|
||||
openVerPlanModal(p) {
|
||||
this.verPlanData = p;
|
||||
|
||||
Reference in New Issue
Block a user