feat: mis cobranzas, recordatorios de pago y agente IA con acciones
- PlanPago.vendedorId + filtro ?mios=1; toggle Mis planes/Todos en /pay - Motor de recordatorios: próximos a vencer (3d) y vencidos (re-cada 7d), canal Monday Actividad + stub WhatsApp listo para WA CRM, log persistente, cron diario 9:30, run manual con dry-run, historial en /api/cobranza/recordatorios - Agente IA: memoria por usuario (conversaciones aisladas) y tool calling: cotizar_unidad (motor real por desarrollo) y buscar_unidad (inventario Monday) - Tests: api 261, cobranza 75, agente-ia 13
This commit is contained in:
@@ -14,7 +14,8 @@ import {
|
||||
YAxis,
|
||||
} from 'recharts'
|
||||
import type { StatusPlan } from '../types'
|
||||
import { listarPlanes, type ResultadoPlanes } from '../services/repository'
|
||||
import { listarPlanes, miosPorDefecto, type ResultadoPlanes } from '../services/repository'
|
||||
import { getSesion } from '../services/auth'
|
||||
import {
|
||||
alCorriente,
|
||||
avancePlan,
|
||||
@@ -55,11 +56,17 @@ export default function DashboardPage() {
|
||||
const paramOrden = searchParams.get('sort')
|
||||
const orden: ColumnaOrden | null = esColumnaOrden(paramOrden) ? paramOrden : null
|
||||
const dir: 'asc' | 'desc' = searchParams.get('dir') === 'desc' ? 'desc' : 'asc'
|
||||
// Toggle "Mis planes / Todos": persiste en la URL (?vista=mios|todos); sin
|
||||
// param, los vendedores arrancan en "Mis planes" y el resto en "Todos"
|
||||
const paramVista = searchParams.get('vista')
|
||||
const mios = paramVista !== null ? paramVista === 'mios' : miosPorDefecto(getSesion()?.usuario.rol)
|
||||
const hoy = useMemo(() => new Date(), [])
|
||||
|
||||
useEffect(() => {
|
||||
let activo = true
|
||||
listarPlanes()
|
||||
setResultado(null)
|
||||
setErrorCarga(null)
|
||||
listarPlanes(undefined, mios)
|
||||
.then((r) => {
|
||||
if (activo) setResultado(r)
|
||||
})
|
||||
@@ -69,7 +76,7 @@ export default function DashboardPage() {
|
||||
return () => {
|
||||
activo = false
|
||||
}
|
||||
}, [])
|
||||
}, [mios])
|
||||
|
||||
const planes = useMemo(() => resultado?.planes ?? [], [resultado])
|
||||
|
||||
@@ -287,6 +294,29 @@ export default function DashboardPage() {
|
||||
|
||||
{/* Filtros */}
|
||||
<div className="flex flex-wrap items-center gap-4">
|
||||
{/* Toggle Mis planes / Todos (?mios=1 filtra en el API por el vendedor del token) */}
|
||||
<div className="flex border border-neutral-300" role="group" aria-label="Alcance de planes">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => actualizarParams({ vista: mios ? null : 'mios' })}
|
||||
aria-pressed={mios}
|
||||
className={`px-4 py-2.5 text-xs font-bold uppercase tracking-widest transition-colors duration-150 ${
|
||||
mios ? 'bg-accent text-white' : 'bg-white text-neutral-600 hover:text-neutral-900'
|
||||
}`}
|
||||
>
|
||||
Mis planes
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => actualizarParams({ vista: mios ? 'todos' : null })}
|
||||
aria-pressed={!mios}
|
||||
className={`px-4 py-2.5 text-xs font-bold uppercase tracking-widest transition-colors duration-150 ${
|
||||
mios ? 'bg-white text-neutral-600 hover:text-neutral-900' : 'bg-accent text-white'
|
||||
}`}
|
||||
>
|
||||
Todos
|
||||
</button>
|
||||
</div>
|
||||
<label htmlFor="filtro-busqueda" className="sr-only">
|
||||
Buscar por cliente o unidad
|
||||
</label>
|
||||
|
||||
Reference in New Issue
Block a user