feat(mejoras): 22 mejoras UX en todos los módulos

Mi día: cancelación con motivo, conteo escrituración real, cita sin retroceder, calendario completo
Cobranza: MoraDashboard respeta alcance, hoy dinámico, cancelar con motivo
Landing: contactos paginados, error vs vacío diferenciado
Dashboards: KPI renombrado, drill-down planes activos
Chat: historial recarga, preguntas rotativas, error solo toast
Transversal: shadow-sm corrupto corregido en 9 archivos

553 API tests + 7 apps construidas
This commit is contained in:
2026-09-11 12:48:47 +00:00
parent 89d95eb53b
commit 1ba7aa49da
28 changed files with 518 additions and 160 deletions

View File

@@ -75,16 +75,18 @@ describe('listarContactosEndpoint (GET /api/contactos) — local', () => {
it('lista desde LeadCaptura/Cliente con la misma forma (fuente local)', async () => {
const { res, json } = resFake()
await listarContactosEndpoint({ usuarioToken: { sub: 'u-1' } } as unknown as Request, res)
await listarContactosEndpoint({ usuarioToken: { sub: 'u-1' }, query: {} } as unknown as Request, res)
const lista = json.mock.calls[0]?.[0] as Record<string, unknown>[]
expect(lista[0]).toMatchObject({ id: 'loc_1', nombre: 'ANA DEMO', estado: 'Afluencia', fuente: 'local' })
const respuesta = json.mock.calls[0]?.[0] as { contactos: Record<string, unknown>[]; pagina: number; hayMas: boolean }
expect(respuesta.contactos[0]).toMatchObject({ id: 'loc_1', nombre: 'ANA DEMO', estado: 'Afluencia', fuente: 'local' })
expect(respuesta.pagina).toBe(1)
expect(respuesta.hayMas).toBe(false)
})
it('filtra por el usuario del token (sus clientes y sus capturas)', async () => {
const { res } = resFake()
await listarContactosEndpoint({ usuarioToken: { sub: 'u-1' } } as unknown as Request, res)
await listarContactosEndpoint({ usuarioToken: { sub: 'u-1' }, query: {} } as unknown as Request, res)
expect(prismaMock.cliente.findMany).toHaveBeenCalledWith(
expect.objectContaining({ where: { capturadoPorId: 'u-1' } }),

View File

@@ -5,11 +5,14 @@ import { NotFoundError } from '../utils/errors.js'
/**
* GET /api/contactos (Bearer) → contactos locales (tabla Cliente + LeadCaptura:
* nombre, teléfono, email, estado), lo más reciente primero, máx 100. Filtra
* por usuario actual: clientes capturados por él + leads asignados a él.
* nombre, teléfono, email, estado), lo más reciente primero, paginado de 100 en
* 100 (?pagina=N; el front muestra el límite). Filtra por usuario actual:
* clientes capturados por él + leads asignados a él.
*/
export async function listarContactosEndpoint(req: Request, res: Response): Promise<void> {
res.json(await listarContactosLocal(req.usuarioToken?.sub))
const paginaParam = Number(req.query['pagina'])
const pagina = Number.isFinite(paginaParam) && paginaParam >= 1 ? Math.floor(paginaParam) : 1
res.json(await listarContactosLocal(req.usuarioToken?.sub, pagina))
}
/**