# Filtros "Considerar activos" y "Considerar NCs" — Fase 1 — Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Agregar 2 toggles en `/impuestos` ("Considerar activos" y "Considerar NCs") que cuando están OFF (default) excluyen del cálculo de IVA/ISR las facturas tipo I con uso I01-I08 y las facturas tipo E con cfdi_tipo_relacion=01 respectivamente.

**Architecture:** Frontend agrega 2 booleanos al state de la página de impuestos y los propaga como query params hasta el backend. Backend aplica un fragmento WHERE adicional (helper en módulo neutral `_shared/cfdi-filters.ts`) a todas las queries que escanean `cfdis` dentro del path de impuestos. Funciones compartidas con dashboard (`calcular*PorRegimen`) reciben los flags como params opcionales con default `true` (= include todo) para preservar el comportamiento del dashboard. Cache `metricas_mensuales` queda intacto pero su gate se extiende para fall-through cuando los toggles están OFF; el cache se actualizará en Fase 2 con un schema base+deltas.

**Tech Stack:** Express + TypeScript en API, Next.js 14 + React Query en web, types compartidos en `@horux/shared`. Verificación vía `pnpm typecheck` (no unit tests para esta área per el patrón del repo).

**Spec:** `docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md`

---

## File Structure

### Files to create

```
apps/api/src/services/_shared/cfdi-filters.ts
  └── Helper buildExtraFilters + buildExtraFiltersAlias (módulo neutral)
```

### Files to modify

```
apps/api/src/services/dashboard.service.ts
  └── calcularIngresosPorRegimen + calcularEgresosPorRegimen: agregar 2 params booleanos default true, aplicar buildExtraFilters al WHERE de TODAS las queries internas

apps/api/src/services/impuestos.service.ts
  └── getResumenIva + getIvaMensual: nuevos params + aplicar filtro al WHERE
  └── getResumenIsr + getIsrMensual + getResumenIsrDesglosado: nuevos params + propagar a calcular*PorRegimen
  └── Cache gate de getResumenIva: extender condición para bypass cuando flags ≠ default backend
  └── Subqueries con alias `e` (rama I PPD/07): aplicar buildExtraFiltersAlias

apps/api/src/controllers/impuestos.controller.ts
  └── Helper parseFlag + 5 handlers parsean los 2 query params nuevos

apps/web/lib/api/impuestos.ts
  └── 5 funciones HTTP extendidas con 2 params nuevos

apps/web/lib/hooks/use-impuestos.ts
  └── 5 hooks extendidos con 2 params nuevos (incluir en queryKey)

apps/web/app/(dashboard)/impuestos/page.tsx
  └── 2 useState nuevos + 2 toggle buttons + propagación a hooks
```

---

## Task 1: Crear módulo helper compartido

**Files:**
- Create: `apps/api/src/services/_shared/cfdi-filters.ts`

- [ ] **Step 1: Crear directorio si no existe**

```bash
mkdir -p "C:/Users/chtr1/Downloads/Horux_despacho/apps/api/src/services/_shared"
```

- [ ] **Step 2: Escribir el módulo**

Crear `apps/api/src/services/_shared/cfdi-filters.ts` con el contenido completo:

```ts
/**
 * Helpers para construir fragmentos AND adicionales en WHERE clauses según
 * los toggles "Considerar activos" y "Considerar NCs" de la UI de impuestos.
 *
 * - considerarActivos === false → excluir facturas tipo I con uso de CFDI I01-I08.
 * - considerarNCs === false → excluir facturas tipo E con cfdi_tipo_relacion = '01'.
 *
 * Cuando ambos son true (default backend = "include todo"), retorna string
 * vacío. Esto preserva el comportamiento histórico para callers que no pasan
 * los flags (ej. dashboard, reportes).
 *
 * Las versiones `Alias` se usan en subqueries con alias de tabla
 * (ej. `cfdis e` en SUM_E_REFERENCING_*). Para activos el filtro es no-op
 * en esos subqueries (porque escanean type E), pero el filtro de NCs sí
 * aplica.
 */

const ACTIVOS_USOS = "('I01','I02','I03','I04','I05','I06','I07','I08')";

export function buildExtraFilters(
  considerarActivos: boolean,
  considerarNCs: boolean,
): string {
  const parts: string[] = [];
  if (!considerarActivos) {
    parts.push(`AND NOT (tipo_comprobante = 'I' AND uso_cfdi IN ${ACTIVOS_USOS})`);
  }
  if (!considerarNCs) {
    parts.push(`AND NOT (tipo_comprobante = 'E' AND COALESCE(cfdi_tipo_relacion, '') = '01')`);
  }
  return parts.length > 0 ? ' ' + parts.join(' ') : '';
}

export function buildExtraFiltersAlias(
  alias: string,
  considerarActivos: boolean,
  considerarNCs: boolean,
): string {
  const parts: string[] = [];
  if (!considerarActivos) {
    parts.push(`AND NOT (${alias}.tipo_comprobante = 'I' AND ${alias}.uso_cfdi IN ${ACTIVOS_USOS})`);
  }
  if (!considerarNCs) {
    parts.push(`AND NOT (${alias}.tipo_comprobante = 'E' AND COALESCE(${alias}.cfdi_tipo_relacion, '') = '01')`);
  }
  return parts.length > 0 ? ' ' + parts.join(' ') : '';
}
```

- [ ] **Step 3: Verificar typecheck del API**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS sin errores.

- [ ] **Step 4: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/services/_shared/cfdi-filters.ts
git commit -m "feat(api): helper buildExtraFilters para toggles activos/NCs"
```

---

## Task 2: Extender `calcularIngresosPorRegimen` y `calcularEgresosPorRegimen` en dashboard.service.ts

**Files:**
- Modify: `apps/api/src/services/dashboard.service.ts`

**Heads up:** Dashboard también consume estas funciones. Default `true` en los nuevos params preserva su comportamiento.

- [ ] **Step 1: Agregar import del helper al inicio del archivo**

Encontrar la sección de imports al inicio de `dashboard.service.ts` y agregar:

```ts
import { buildExtraFilters } from './_shared/cfdi-filters.js';
```

(Las imports en este proyecto usan extensión `.js` aunque el archivo sea `.ts` — patrón ESM con tsx. Revisa imports existentes para confirmar el estilo.)

- [ ] **Step 2: Extender la signature de `calcularIngresosPorRegimen`**

Buscar la función exportada `calcularIngresosPorRegimen`. Agregar 2 parámetros opcionales con default `true` al final de la lista, antes del cierre de `)`:

Cambiar la signature para incluir:

```ts
export async function calcularIngresosPorRegimen(
  pool: Pool,
  tenantId: string,
  fechaInicio: string,
  fechaFin: string,
  // ...parámetros existentes preservados...
  conciliacion?: boolean,
  contribuyenteId?: string | null,
  considerarActivos: boolean = true,  // nuevo
  considerarNCs: boolean = true,       // nuevo
): Promise<...>
```

(Mantener los nombres y orden de los parámetros existentes. Solo agregar los 2 nuevos al final.)

- [ ] **Step 3: Aplicar el filtro a TODAS las queries internas de calcularIngresosPorRegimen**

Dentro del cuerpo de la función, antes de las queries SQL, computar el fragmento:

```ts
const extra = buildExtraFilters(considerarActivos, considerarNCs);
```

Luego, en cada query SQL que escanee `cfdis`, agregar `${extra}` al final del WHERE clause. Buscar todos los `FROM cfdis` dentro del cuerpo de la función — deben ser ~3-5 queries — y a cada uno agregarle el fragmento.

Ejemplo de transformación:

```ts
// Antes:
const { rows } = await pool.query(`
  SELECT ...
  FROM cfdis
  WHERE ${VIGENTE} AND ${FR}
    AND ${ctx.esEmisor}
  GROUP BY ...
`, [fechaInicio, fechaFin]);

// Después:
const { rows } = await pool.query(`
  SELECT ...
  FROM cfdis
  WHERE ${VIGENTE} AND ${FR}${extra}
    AND ${ctx.esEmisor}
  GROUP BY ...
`, [fechaInicio, fechaFin]);
```

`extra` retorna con leading space cuando agrega contenido. Si ambos flags son `true` retorna string vacío y la query queda idéntica.

- [ ] **Step 4: Repetir para `calcularEgresosPorRegimen`**

Misma extensión de signature (2 params al final con default `true`), mismo helper `extra = buildExtraFilters(...)`, misma aplicación a todos los `FROM cfdis` del cuerpo.

- [ ] **Step 5: Verificar typecheck del API**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS sin errores. Cualquier callsite existente de estas funciones que no pase los nuevos params usa los defaults `true`, comportamiento idéntico a antes.

- [ ] **Step 6: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/services/dashboard.service.ts
git commit -m "feat(api): calcular*PorRegimen aceptan flags considerarActivos/considerarNCs"
```

---

## Task 3: Extender `getResumenIva` y `getIvaMensual` en impuestos.service.ts

**Files:**
- Modify: `apps/api/src/services/impuestos.service.ts`

- [ ] **Step 1: Agregar import del helper al inicio del archivo**

Buscar la sección de imports del archivo. Agregar:

```ts
import { buildExtraFilters } from './_shared/cfdi-filters.js';
```

- [ ] **Step 2: Extender signature de `getResumenIva`**

Encontrar `export async function getResumenIva(...)`. Agregar 2 params al final con default `true`:

```ts
export async function getResumenIva(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  tenantId: string,
  conciliacion?: boolean,
  contribuyenteId?: string | null,
  considerarActivos: boolean = true,
  considerarNCs: boolean = true,
): Promise<ResumenIva>
```

- [ ] **Step 3: Computar `extra` y aplicar a todas las queries internas**

Dentro del body, después de `const FR = getFR(conciliacion);` agregar:

```ts
const extra = buildExtraFilters(considerarActivos, considerarNCs);
```

Y aplicar `${extra}` al final de cada WHERE en queries con `FROM cfdis` (las que NO usan alias `e` — esas son Task 5). Aplica el mismo patrón del Task 2 Step 3.

- [ ] **Step 4: Extender el cache gate de getResumenIva**

Buscar la condición que protege el path de cache (alrededor de línea 322 según la versión actual del archivo, puede haber cambiado por WIP). El patrón es:

```ts
if (
  !conciliacion &&
  contribuyenteId &&
  ...condiciones existentes...
) {
  const cached = await readResumenIvaFromCache(...);
  if (cached) return cached;
}
```

Extender:

```ts
if (
  !conciliacion &&
  considerarActivos &&  // nuevo: cache solo aplica con backend default (todo incluido)
  considerarNCs &&       // nuevo
  contribuyenteId &&
  ...condiciones existentes...
) {
  const cached = await readResumenIvaFromCache(...);
  if (cached) return cached;
}
```

Cuando UI tiene los toggles OFF (default), `considerarActivos===false || considerarNCs===false` → cache bypass → live query. Aceptado para Fase 1.

- [ ] **Step 5: Extender signature de `getIvaMensual`**

Misma extensión: agregar 2 params al final con default `true`. Agregar `const extra = buildExtraFilters(...)` y aplicar a todas las queries con `FROM cfdis` dentro del loop mensual.

- [ ] **Step 6: Verificar typecheck del API**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS.

- [ ] **Step 7: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/services/impuestos.service.ts
git commit -m "feat(api): getResumenIva y getIvaMensual aceptan flags considerarActivos/considerarNCs + cache gate"
```

---

## Task 4: Extender `getResumenIsr`, `getIsrMensual`, `getResumenIsrDesglosado`

**Files:**
- Modify: `apps/api/src/services/impuestos.service.ts`

- [ ] **Step 1: Extender signature de `getResumenIsr`**

Agregar 2 params al final con default `true`:

```ts
export async function getResumenIsr(
  pool: Pool,
  fechaInicio: string,
  fechaFin: string,
  tenantId: string,
  conciliacion?: boolean,
  contribuyenteId?: string | null,
  considerarActivos: boolean = true,
  considerarNCs: boolean = true,
): Promise<ResumenIsr>
```

- [ ] **Step 2: Propagar a llamadas a `calcular*PorRegimen` y a queries internas**

Dentro de `getResumenIsr`:
- Agregar `const extra = buildExtraFilters(considerarActivos, considerarNCs);` al inicio del cuerpo (después del `getFR`).
- Aplicar `${extra}` a TODOS los `FROM cfdis` internos de la función (sin alias).
- En las llamadas existentes `calcularIngresosPorRegimen(pool, tenantId, fechaInicio, fechaFin, undefined, undefined, conciliacion, contribuyenteId)` agregar al final los 2 nuevos args:

```ts
const ingresosData = await calcularIngresosPorRegimen(
  pool, tenantId, fechaInicio, fechaFin,
  undefined, undefined, conciliacion, contribuyenteId,
  considerarActivos, considerarNCs,  // nuevos
);
```

Idem para `calcularEgresosPorRegimen`.

- [ ] **Step 3: Extender signature de `getIsrMensual`**

Agregar 2 params al final con default `true`:

```ts
export async function getIsrMensual(
  pool: Pool,
  año: number,
  tenantId: string,
  conciliacion?: boolean,
  contribuyenteId?: string | null,
  regimenClave?: string | null,
  considerarActivos: boolean = true,
  considerarNCs: boolean = true,
): Promise<IsrMensual[]>
```

- [ ] **Step 4: Propagar dentro de `getIsrMensual`**

Dentro del loop mensual de `getIsrMensual`, las llamadas existentes a `calcularIngresosPorRegimen` y `calcularEgresosPorRegimen` deben recibir los 2 nuevos args al final. Patrón:

```ts
const [ingresosData, egresosData] = await Promise.all([
  calcularIngresosPorRegimen(
    pool, tenantId, fi, ff,
    undefined, undefined, conciliacion, contribuyenteId,
    considerarActivos, considerarNCs,  // nuevos
  ),
  calcularEgresosPorRegimen(
    pool, tenantId, fi, ff,
    undefined, undefined, conciliacion, contribuyenteId,
    considerarActivos, considerarNCs,  // nuevos
  ),
]);
```

- [ ] **Step 5: Extender signature de `getResumenIsrDesglosado`**

Agregar 2 params al final con default `true`:

```ts
export async function getResumenIsrDesglosado(
  pool: Pool,
  fechaFin: string,
  tenantId: string,
  conciliacion?: boolean,
  contribuyenteId?: string | null,
  considerarActivos: boolean = true,
  considerarNCs: boolean = true,
): Promise<import('@horux/shared').ResumenIsrDesglosado>
```

- [ ] **Step 6: Propagar dentro de `getResumenIsrDesglosado`**

Las 3 llamadas a `getResumenIsr` (una secuencial para `anteriores` cuando mesFinal !== 1, dos en `Promise.all` para `delPeriodo` y `total`) deben pasar los 2 nuevos args al final:

```ts
anteriores = await getResumenIsr(
  pool, fiAnt, ffAnt, tenantId, conciliacion, contribuyenteId,
  considerarActivos, considerarNCs,  // nuevos
);

const [delPeriodo, total] = await Promise.all([
  getResumenIsr(pool, fiPeriodo, ffPeriodo, tenantId, conciliacion, contribuyenteId, considerarActivos, considerarNCs),
  getResumenIsr(pool, fiTotal, ffTotal, tenantId, conciliacion, contribuyenteId, considerarActivos, considerarNCs),
]);
```

- [ ] **Step 7: Verificar typecheck del API**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS.

- [ ] **Step 8: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/services/impuestos.service.ts
git commit -m "feat(api): getResumenIsr/getIsrMensual/getResumenIsrDesglosado aceptan flags considerarActivos/considerarNCs"
```

---

## Task 5: Aplicar filtros a subqueries con alias `e` (rama I PPD/07)

**Files:**
- Modify: `apps/api/src/services/impuestos.service.ts`

**Context:** En la rama I PPD/07 hay subqueries que iteran sobre `cfdis e` (alias) para detectar E que referencian I PPD/07. Estos subqueries pueden ser constants templates (`SUM_E_REFERENCING_TRAS`, `SUM_E_REFERENCING_RET`, `HAS_E_REFERENCING_MISMO_MES`) o expresiones inline. Necesitan el filtro `buildExtraFiltersAlias('e', ...)`.

- [ ] **Step 1: Importar `buildExtraFiltersAlias`**

Verificar que el import al inicio del archivo incluya ambas:

```ts
import { buildExtraFilters, buildExtraFiltersAlias } from './_shared/cfdi-filters.js';
```

- [ ] **Step 2: Identificar y modificar las constantes/templates de subqueries con alias `e`**

Buscar `cfdis e` en `impuestos.service.ts`. Deberían aparecer en constantes como `SUM_E_REFERENCING_TRAS = (esLadoE: string) => \`...\`` y similares.

**Decisión arquitectónica**: estas constantes son templates funcionales. La forma más limpia es **convertirlas a funciones que reciben los flags** y los aplican.

Buscar las constantes existentes (típicamente templates string functions) y convertirlas. Ejemplo (la firma exacta existente puede variar; la idea es agregar los 2 params al final):

Si encuentras (formato actual aproximado):

```ts
const SUM_E_REFERENCING_TRAS = (esLadoE: string) => `COALESCE((
  SELECT SUM(${IVA_TRAS_EXPR_ALIAS('e')})
    FROM cfdis e
   WHERE e.status NOT IN ('Cancelado', '0')
     AND ${esLadoE}
     AND ...resto del where...
), 0)`;
```

Cambiar a:

```ts
const SUM_E_REFERENCING_TRAS = (esLadoE: string, considerarActivos: boolean, considerarNCs: boolean) => `COALESCE((
  SELECT SUM(${IVA_TRAS_EXPR_ALIAS('e')})
    FROM cfdis e
   WHERE e.status NOT IN ('Cancelado', '0')
     AND ${esLadoE}
     AND ...resto del where...${buildExtraFiltersAlias('e', considerarActivos, considerarNCs)}
), 0)`;
```

Aplicar el mismo patrón a las demás subqueries con alias `e`:
- `SUM_E_REFERENCING_TRAS`
- `SUM_E_REFERENCING_RET`
- `HAS_E_REFERENCING_MISMO_MES`
- Cualquier otra que use `cfdis e`

- [ ] **Step 3: Actualizar callsites de las subqueries**

Buscar dónde se usan estas funciones (ej. dentro de `getResumenIva`, `getResumenIsr`, sus helpers `bucketCausadoNeg`, `bucketAcreditableNeg`, etc.) y agregar los nuevos params:

```ts
// Antes:
SUM_E_REFERENCING_TRAS(esLado)

// Después:
SUM_E_REFERENCING_TRAS(esLado, considerarActivos, considerarNCs)
```

Los callsites están dentro de funciones que ya recibieron los flags en Tasks 3 y 4. Solo es propagación local.

- [ ] **Step 4: Verificar typecheck del API**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS.

- [ ] **Step 5: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/services/impuestos.service.ts
git commit -m "feat(api): subqueries con alias 'e' (I PPD/07) respetan flags considerarActivos/considerarNCs"
```

---

## Task 6: Controllers — `parseFlag` helper + propagación

**Files:**
- Modify: `apps/api/src/controllers/impuestos.controller.ts`

- [ ] **Step 1: Agregar helper `parseFlag` cerca del top del archivo**

Después del helper `parseConciliacion(req)` existente, agregar:

```ts
function parseFlag(req: Request, key: string, defaultValue = true): boolean {
  const v = req.query[key];
  if (v === undefined || v === null) return defaultValue;
  return v === 'true' || v === '1';
}
```

- [ ] **Step 2: Extender los 5 handlers**

Para cada uno de los 5 handlers (`getIvaMensual`, `getIsrMensual`, `getResumenIva`, `getResumenIsr`, `getResumenIsrDesglosado`):

1. Agregar las 2 lecturas de query params después de las existentes:

   ```ts
   const considerarActivos = parseFlag(req, 'considerarActivos', true);
   const considerarNCs = parseFlag(req, 'considerarNCs', true);
   ```

2. Pasar al service como los 2 últimos args.

Ejemplo para `getResumenIsrDesglosado`:

```ts
export async function getResumenIsrDesglosado(req: Request, res: Response, next: NextFunction) {
  try {
    if (!req.tenantPool) {
      return next(new AppError(400, 'Tenant no configurado'));
    }
    const now = new Date();
    const y = now.getFullYear();
    const m = now.getMonth() + 1;
    const lastDay = new Date(y, m, 0).getDate();
    const fechaFin = (req.query.fechaFin as string) || `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}`;
    const conciliacion = parseConciliacion(req);
    const contribuyenteId = (req.query.contribuyenteId as string) || null;
    const considerarActivos = parseFlag(req, 'considerarActivos', true);  // nuevo
    const considerarNCs = parseFlag(req, 'considerarNCs', true);            // nuevo

    const desglose = await impuestosService.getResumenIsrDesglosado(
      req.tenantPool,
      fechaFin,
      effectiveTenantId(req),
      conciliacion,
      contribuyenteId,
      considerarActivos,  // nuevo
      considerarNCs,       // nuevo
    );
    res.json(desglose);
  } catch (error) {
    next(error);
  }
}
```

Aplicar el mismo patrón a los otros 4 handlers (`getIvaMensual`, `getIsrMensual`, `getResumenIva`, `getResumenIsr`).

- [ ] **Step 3: Verificar typecheck**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/api typecheck`
Expected: PASS.

- [ ] **Step 4: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/api/src/controllers/impuestos.controller.ts
git commit -m "feat(api): controllers parsean flags considerarActivos/considerarNCs y los propagan al service"
```

---

## Task 7: Frontend API client

**Files:**
- Modify: `apps/web/lib/api/impuestos.ts`

- [ ] **Step 1: Extender las 5 funciones HTTP**

Para cada función, agregar 2 params booleanos opcionales y serializarlos en `URLSearchParams`. Patrón:

```ts
export async function getResumenIsrDesglosado(
  fechaFin: string,
  conciliacion?: boolean,
  considerarActivos?: boolean,
  considerarNCs?: boolean,
  contribuyenteId?: string | null,
): Promise<ResumenIsrDesglosado> {
  const params = new URLSearchParams();
  params.set('fechaFin', fechaFin);
  if (conciliacion) params.set('conciliacion', 'true');
  if (considerarActivos) params.set('considerarActivos', 'true');
  if (considerarNCs) params.set('considerarNCs', 'true');
  if (contribuyenteId) params.set('contribuyenteId', contribuyenteId);
  const response = await apiClient.get<ResumenIsrDesglosado>(`/impuestos/isr/resumen-desglosado?${params}`);
  return response.data;
}
```

Aplicar el mismo patrón a:
- `getIsrMensual(año, conciliacion, contribuyenteId, regimenClave, considerarActivos, considerarNCs)` — orden: insertar los 2 nuevos AL FINAL para no romper callers existentes que pasan posicionalmente.
- `getIvaMensual(año, conciliacion, contribuyenteId, considerarActivos, considerarNCs)`
- `getResumenIva(fechaInicio, fechaFin, conciliacion, contribuyenteId, considerarActivos, considerarNCs)`
- `getResumenIsr(fechaInicio, fechaFin, conciliacion, contribuyenteId, considerarActivos, considerarNCs)`
- `getResumenIsrDesglosado(fechaFin, conciliacion, considerarActivos, considerarNCs, contribuyenteId)` — la signature actual ya tiene `contribuyenteId` al final; mantenerlo allí.

**Importante**: solo set en URLSearchParams cuando el valor es `true`. Si el frontend pasa `undefined` o `false`, NO se manda el param (el backend default `true` aplica). Esto evita ambigüedad con la convención `'false'` string.

Espera — esta regla es la INVERSA de lo que queremos. Nuestro UI default es `false` (toggle OFF) y queremos QUE EL BACKEND EXCLUYA. Si el frontend NO manda el param cuando el toggle está OFF, el backend default `true` (include) aplica → no se excluye → COMPORTAMIENTO INCORRECTO.

Corrección: serializar el booleano explícitamente (siempre).

```ts
if (considerarActivos !== undefined) params.set('considerarActivos', String(considerarActivos));
if (considerarNCs !== undefined) params.set('considerarNCs', String(considerarNCs));
```

Y en el controller (ya implementado en Task 6) `parseFlag` retorna `false` cuando `req.query.considerarActivos === 'false'`.

Verificar que el `parseFlag` del Task 6 maneja el string `'false'`:

```ts
function parseFlag(req: Request, key: string, defaultValue = true): boolean {
  const v = req.query[key];
  if (v === undefined || v === null) return defaultValue;
  return v === 'true' || v === '1';  // cualquier otra cosa (ej. 'false', '0') → false
}
```

`v === 'true' || v === '1'` retorna `false` cuando `v === 'false'`. Correcto.

Aplicar a los 5 funciones:

```ts
if (considerarActivos !== undefined) params.set('considerarActivos', String(considerarActivos));
if (considerarNCs !== undefined) params.set('considerarNCs', String(considerarNCs));
```

- [ ] **Step 2: Verificar typecheck del web**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/web exec tsc --noEmit 2>&1 | grep "lib/api/impuestos"`
Expected: NO output (clean — los errores pre-existentes en otros archivos del web no nos importan).

- [ ] **Step 3: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/web/lib/api/impuestos.ts
git commit -m "feat(web): API client funciones aceptan considerarActivos/considerarNCs"
```

---

## Task 8: Frontend hooks

**Files:**
- Modify: `apps/web/lib/hooks/use-impuestos.ts`

- [ ] **Step 1: Extender los 5 hooks con 2 params nuevos**

Para cada hook, agregar 2 params booleanos opcionales al final, incluirlos en `queryKey`, y pasarlos al API call. Patrón:

```ts
export function useResumenIsrDesglosado(
  fechaFin: string,
  conciliacion?: boolean,
  considerarActivos?: boolean,
  considerarNCs?: boolean,
) {
  const tk = useTenantKey();
  const { selectedContribuyenteId } = useContribuyenteStore();
  return useQuery({
    queryKey: ['isr-resumen-desglosado', tk, fechaFin, conciliacion, considerarActivos, considerarNCs, selectedContribuyenteId],
    queryFn: () => impuestosApi.getResumenIsrDesglosado(fechaFin, conciliacion, considerarActivos, considerarNCs, selectedContribuyenteId),
    enabled: !!fechaFin,
  });
}
```

Aplicar a los 5 hooks: `useResumenIsrDesglosado`, `useResumenIsr`, `useResumenIva`, `useIsrMensual`, `useIvaMensual`.

Para `useIsrMensual` que ya tiene `regimenClave` opcional, mantener ese param y agregar los 2 nuevos al final:

```ts
export function useIsrMensual(
  año?: number,
  conciliacion?: boolean,
  regimenClave?: string | null,
  considerarActivos?: boolean,
  considerarNCs?: boolean,
)
```

(Verificar el orden actual de params del hook — los nuevos van AL FINAL.)

- [ ] **Step 2: Verificar typecheck del web**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/web exec tsc --noEmit 2>&1 | grep "lib/hooks/use-impuestos"`
Expected: NO output.

- [ ] **Step 3: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add apps/web/lib/hooks/use-impuestos.ts
git commit -m "feat(web): hooks de impuestos aceptan considerarActivos/considerarNCs en queryKey"
```

---

## Task 9: Frontend UI — toggles + propagación

**Files:**
- Modify: `apps/web/app/(dashboard)/impuestos/page.tsx`

- [ ] **Step 1: Agregar 2 useState al inicio del componente**

Buscar la sección de useState existente (cerca de líneas 30-40, donde está `useState(false)` para `conciliacion`). Agregar:

```ts
const [considerarActivos, setConsiderarActivos] = useState(false);
const [considerarNCs, setConsiderarNCs] = useState(false);
```

- [ ] **Step 2: Pasar los 2 nuevos states a TODOS los hooks de impuestos**

Buscar cada llamada a hook y agregar los 2 args al final. Patrón:

```ts
// Antes:
const { data: resumenIsr } = useResumenIsr(fechaInicio, fechaFin, conciliacion);
const { data: resumenIsrDesglose } = useResumenIsrDesglosado(fechaFin, conciliacion);

// Después:
const { data: resumenIsr } = useResumenIsr(fechaInicio, fechaFin, conciliacion, considerarActivos, considerarNCs);
const { data: resumenIsrDesglose } = useResumenIsrDesglosado(fechaFin, conciliacion, considerarActivos, considerarNCs);
```

Aplicar a:
- `useIvaMensual(año, conciliacion, considerarActivos, considerarNCs)`
- `useIsrMensual(año, conciliacion, regimenSeleccionado, considerarActivos, considerarNCs)`
- `useResumenIva(fechaInicio, fechaFin, conciliacion, considerarActivos, considerarNCs)`
- `useResumenIsr(fechaInicio, fechaFin, conciliacion, considerarActivos, considerarNCs)`
- `useResumenIsrDesglosado(fechaFin, conciliacion, considerarActivos, considerarNCs)`

- [ ] **Step 3: Agregar 2 toggle buttons al row de filtros**

Buscar el bloque del toggle de Conciliación (alrededor de líneas 92-103). Después del button de Conciliación y antes del cierre del `<div className="flex items-center gap-3">`, agregar:

```tsx
<button
  onClick={() => setConsiderarActivos(!considerarActivos)}
  className={cn(
    'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
    considerarActivos
      ? 'bg-primary/10 text-primary border border-primary/30'
      : 'hover:bg-accent'
  )}
  title="Si está inactivo, no se consideran facturas tipo I con uso de CFDI I01-I08 (compras de activos fijos)."
>
  <CheckSquare className="h-4 w-4" />
  Considerar activos
</button>
<button
  onClick={() => setConsiderarNCs(!considerarNCs)}
  className={cn(
    'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
    considerarNCs
      ? 'bg-primary/10 text-primary border border-primary/30'
      : 'hover:bg-accent'
  )}
  title="Si está inactivo, no se consideran facturas tipo E con tipo de relación 01 (notas de crédito)."
>
  <CheckSquare className="h-4 w-4" />
  Considerar NCs
</button>
```

`CheckSquare` y `cn` ya están importados al inicio del archivo. NO agregues imports nuevos.

- [ ] **Step 4: Verificar typecheck del web**

Run: `cd C:/Users/chtr1/Downloads/Horux_despacho && pnpm --filter @horux/web exec tsc --noEmit 2>&1 | grep "impuestos/page"`
Expected: NO output.

- [ ] **Step 5: Smoke (opcional, defer si dev no corre)**

Si dev corre (`curl -s -o /dev/null -w "%{http_code}" http://localhost:3000 2>/dev/null` retorna algo distinto de 000):

1. Abrir `/impuestos`, pestaña ISR. Confirmar que aparecen 3 toggles: Conciliación, Considerar activos, Considerar NCs (todos OFF inicialmente).
2. Tooltip al hover en cada toggle nuevo describe el filtro.
3. Click "Considerar activos" → cambia a estilo activo (azul).
4. Verificar que los números de la tabla y la sección "Cálculo de ISR del Periodo" recalculan al togglear.
5. Smoke completo cross-feature en Task 10.

Si dev NO corre, **NO lo inicies**. Skip.

- [ ] **Step 6: Commit**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
git add "apps/web/app/(dashboard)/impuestos/page.tsx"
git commit -m "feat(web): toggles 'Considerar activos' y 'Considerar NCs' en /impuestos"
```

---

## Task 10: Verificación final + sync OneDrive + commit V.1.0.7

**Files:**
- Verify: typecheck completo
- Smoke: cross-feature en browser
- Copy: 8 archivos a OneDrive (1 nuevo + 7 modificados)
- Commit: V.1.0.7

- [ ] **Step 1: Typecheck completo de shared + api**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
pnpm --filter @horux/shared typecheck
pnpm --filter @horux/api typecheck
```

Expected: ambos PASS sin errores. Si falla, **STOP y reporta**.

- [ ] **Step 2: Verificar archivos web del plan limpios**

```bash
cd C:/Users/chtr1/Downloads/Horux_despacho
pnpm --filter @horux/web exec tsc --noEmit 2>&1 | grep -E "(lib/api/impuestos|lib/hooks/use-impuestos|impuestos/page)"
```

Expected: NO output (los 3 archivos web del plan están limpios; otros errores web son pre-existentes y fuera de scope).

- [ ] **Step 3: Smoke cross-feature**

Si dev corre y tienes acceso al browser:

1. **Default UI** (`/impuestos`, ambos toggles OFF):
   - ISR/IVA cargan números menores que antes (excluyen activos + NCs).
   - Tabla "Histórico ISR" usa los acumulados filtrados.
   - Sección "Cálculo de ISR del Periodo" refleja los filtros consistentemente en `delPeriodo`, `anteriores`, `total`.
2. **Toggle "Considerar activos" ON**: ingresos/deducciones/base gravable suben con la suma de activos del periodo.
3. **Toggle "Considerar NCs" ON**: cambia el bucket — NCs aparecen restando.
4. **Combinaciones**: probar las 4 combinaciones de los 2 toggles + Conciliación on/off (8 total).
5. **Cross-check `/dashboard`**: KPIs (ingresos, gastos, utilidad) **NO cambian** vs antes del deploy. Esto valida que el default `true` en `calcular*PorRegimen` preserva el dashboard.
6. **Activos Fijos tab**: la tabla sigue mostrando todos los CFDIs I con uso I01-I08 (no afectada por el toggle "Considerar activos" en ISR/IVA).
7. **Cambiar contribuyente**: el state de los toggles persiste en sesión (no se resetea al cambiar contribuyente).

Si no puedes hacer smoke completo, reporta qué se verificó y qué quedó pendiente para el owner.

- [ ] **Step 4: Copiar archivos a OneDrive (8 archivos: 1 nuevo + 7 modificados)**

```bash
SRC="C:/Users/chtr1/Downloads/Horux_despacho"
DST="C:/Users/chtr1/OneDrive/Documentos/GitHub/Horux_despachos"

# Crear carpeta _shared si no existe en OneDrive
mkdir -p "$DST/apps/api/src/services/_shared"

cp -p "$SRC/apps/api/src/services/_shared/cfdi-filters.ts"      "$DST/apps/api/src/services/_shared/cfdi-filters.ts"
cp -p "$SRC/apps/api/src/services/dashboard.service.ts"          "$DST/apps/api/src/services/dashboard.service.ts"
cp -p "$SRC/apps/api/src/services/impuestos.service.ts"          "$DST/apps/api/src/services/impuestos.service.ts"
cp -p "$SRC/apps/api/src/controllers/impuestos.controller.ts"    "$DST/apps/api/src/controllers/impuestos.controller.ts"
cp -p "$SRC/apps/web/lib/api/impuestos.ts"                       "$DST/apps/web/lib/api/impuestos.ts"
cp -p "$SRC/apps/web/lib/hooks/use-impuestos.ts"                 "$DST/apps/web/lib/hooks/use-impuestos.ts"
cp -p "$SRC/apps/web/app/(dashboard)/impuestos/page.tsx"         "$DST/apps/web/app/(dashboard)/impuestos/page.tsx"
cp -p "$SRC/docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md" "$DST/docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md"
cp -p "$SRC/docs/superpowers/plans/2026-04-27-filtros-activos-ncs-impuestos-fase1.md"        "$DST/docs/superpowers/plans/2026-04-27-filtros-activos-ncs-impuestos-fase1.md"
```

- [ ] **Step 5: Verificar diff Downloads vs OneDrive**

```bash
diff -rq \
  --exclude=node_modules --exclude=.git --exclude=.turbo --exclude=.next \
  --exclude=dist --exclude=tsconfig.tsbuildinfo --exclude=email-previews \
  --exclude=pnpm-lock.yaml --exclude=.env --exclude=.env.local \
  "C:/Users/chtr1/Downloads/Horux_despacho" \
  "C:/Users/chtr1/OneDrive/Documentos/GitHub/Horux_despachos"
```

Expected: única diferencia esperada es `Only in C:/Users/chtr1/Downloads/Horux_despacho/apps/api: data` (XMLs runtime). Si aparece otra cosa, **STOP y reporta**.

- [ ] **Step 6: Commit en OneDrive**

```bash
cd "C:/Users/chtr1/OneDrive/Documentos/GitHub/Horux_despachos"
git status --short
```

Confirma que aparezcan exactamente los archivos copiados como M (modified) o ?? (untracked). Si hay algo más, reporta.

```bash
git add \
  apps/api/src/services/_shared/cfdi-filters.ts \
  apps/api/src/services/dashboard.service.ts \
  apps/api/src/services/impuestos.service.ts \
  apps/api/src/controllers/impuestos.controller.ts \
  apps/web/lib/api/impuestos.ts \
  apps/web/lib/hooks/use-impuestos.ts \
  "apps/web/app/(dashboard)/impuestos/page.tsx" \
  docs/superpowers/specs/2026-04-27-filtros-activos-ncs-impuestos-fase1-design.md \
  docs/superpowers/plans/2026-04-27-filtros-activos-ncs-impuestos-fase1.md

git commit -m "V.1.0.7"

git status --short
git log -2 --oneline
```

Expected:
- Commit creado con hash nuevo, mensaje `V.1.0.7`.
- Working tree clean.
- `git log -2` muestra V.1.0.7 sobre V.1.0.6.

- [ ] **Step 7: NO push**

Push lo hace el owner manualmente. Confirmar explícitamente que NO se ejecutó `git push`.
