# Drill-down genérica — sort por nombre emisor/receptor

## Contexto

La tabla de la página `/drill-down` (apps/web/app/(dashboard)/drill-down/page.tsx)
actualmente permite ordenar por `Fecha`, `Total MXN`, `Monto Pago` e `IVA Trasl.`
mediante el hook `useTableSort` y el componente `SortableHeader` de
`@horux/shared-ui`. Las columnas `Nombre Emisor` y `Nombre Receptor` se
renderizan como `<th>` planos no ordenables.

## Objetivo

Permitir ordenar también por nombre del emisor y por nombre del receptor,
sin remover ninguna de las columnas ordenables existentes.

Alcance limitado a la drill-down genérica. Las 9 páginas de `/alertas/*` quedan
fuera de este cambio (decisión del owner — se evaluarán después).

## Cambios

Archivo único: `apps/web/app/(dashboard)/drill-down/page.tsx`.

1. Extender el segundo parámetro de tipo de `useTableSort` para incluir las
   nuevas keys:

   ```ts
   useTableSort<Cfdi, 'fecha' | 'total' | 'pago' | 'iva' | 'emisor' | 'receptor'>
   ```

2. Agregar dos accesores al objeto pasado al hook:

   ```ts
   emisor:   (c) => c.nombreEmisor || '',
   receptor: (c) => c.nombreReceptor || '',
   ```

   `useTableSort` ya soporta accesores de tipo `string` — usa
   `String.prototype.localeCompare` cuando ambos valores son strings, lo cual
   maneja la collation del español correctamente.

3. Reemplazar los dos `<th>` planos por `SortableHeader`:

   ```tsx
   // antes
   <th className="pb-3 font-medium">Nombre Emisor</th>
   <th className="pb-3 font-medium">Nombre Receptor</th>

   // después
   <SortableHeader label="Nombre Emisor"
                   active={getSortIndicator('emisor')}
                   onClick={() => toggleSort('emisor')} />
   <SortableHeader label="Nombre Receptor"
                   active={getSortIndicator('receptor')}
                   onClick={() => toggleSort('receptor')} />
   ```

4. Mantener el `initialKey = 'fecha'` y `initialDir = 'desc'` (default actual).

## No-cambios

- No se tocan: `useTableSort`, `SortableHeader`, ni cualquier otro archivo en
  `@horux/shared-ui`.
- No se tocan controllers ni services del API. El sort es 100% client-side.
- No se tocan las columnas RFC Emisor, RFC Receptor, UUID, Comp., M. Pago,
  Reg. E ni Reg. R — siguen siendo `<th>` planos no ordenables.
- No se modifica el export a Excel: ya consume `sortedData`, así que el orden
  vigente del usuario se respeta automáticamente.

## Comportamiento esperado

- Click sobre "Nombre Emisor": ordena ascendente por nombre. Re-click:
  descendente. Cambia el sort activo (un solo sort a la vez, ya es el
  contrato del hook).
- Click sobre "Nombre Receptor": idéntico, reemplaza al sort previo.
- Filas con `nombreEmisor` o `nombreReceptor` null/undefined: el accesor
  retorna string vacío `''`, así que en `asc` aparecen primero. Es el
  comportamiento estándar de `localeCompare` y se considera aceptable
  (un CFDI sin nombre emisor/receptor es raro y debería ser visible al
  ordenar por nombre).

## Riesgo

Mínimo:
- Cambio puramente client-side, una sola página, ~6 líneas netas.
- No introduce dependencias nuevas.
- `pnpm typecheck` debería seguir limpio (las nuevas keys están dentro del
  union genérico, los accesores cumplen el contrato `(row: T) => number | string`).

## Plan de pruebas (smoke)

1. `pnpm typecheck` debe seguir en 0 errores.
2. Abrir `/drill-down` desde cualquier KPI del dashboard.
3. Click en "Nombre Emisor" → verificar orden alfabético ascendente y flecha
   en el header. Re-click → descendente.
4. Click en "Nombre Receptor" → mismo comportamiento.
5. Click en "Fecha" / "Total MXN" → confirmar que los sorts pre-existentes
   siguen funcionando.
6. Exportar a Excel después de ordenar por "Nombre Emisor" → confirmar que
   el archivo descargado mantiene el mismo orden.

## Pendientes derivados

- Replicar el patrón en las 9 páginas de `/alertas/*` (cancelaciones,
  cancelaciones-periodo-anterior, efectivo, tipo-relacion-sospechosa,
  concentracion-clientes, concentracion-proveedores, discrepancia-regimen,
  lista-negra-clientes, lista-negra-proveedores). Decisión del owner cuándo
  abordarlas. Para `lista-negra-*` además habrá que introducir
  `useTableSort` desde cero (hoy no lo usan).
