34 KiB
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
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:
/**
* 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
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:
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:
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:
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:
// 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
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:
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:
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
extray aplicar a todas las queries internas
Dentro del body, después de const FR = getFR(conciliacion); agregar:
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:
if (
!conciliacion &&
contribuyenteId &&
...condiciones existentes...
) {
const cached = await readResumenIvaFromCache(...);
if (cached) return cached;
}
Extender:
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
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:
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*PorRegimeny a queries internas
Dentro de getResumenIsr:
- Agregar
const extra = buildExtraFilters(considerarActivos, considerarNCs);al inicio del cuerpo (después delgetFR). - Aplicar
${extra}a TODOS losFROM cfdisinternos 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:
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:
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:
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:
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:
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
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:
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):
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:
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:
// 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
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
parseFlagcerca del top del archivo
Después del helper parseConciliacion(req) existente, agregar:
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):
-
Agregar las 2 lecturas de query params después de las existentes:
const considerarActivos = parseFlag(req, 'considerarActivos', true); const considerarNCs = parseFlag(req, 'considerarNCs', true); -
Pasar al service como los 2 últimos args.
Ejemplo para getResumenIsrDesglosado:
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
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:
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 tienecontribuyenteIdal 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).
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':
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:
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
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:
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:
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
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:
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:
// 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:
<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):
- Abrir
/impuestos, pestaña ISR. Confirmar que aparecen 3 toggles: Conciliación, Considerar activos, Considerar NCs (todos OFF inicialmente). - Tooltip al hover en cada toggle nuevo describe el filtro.
- Click "Considerar activos" → cambia a estilo activo (azul).
- Verificar que los números de la tabla y la sección "Cálculo de ISR del Periodo" recalculan al togglear.
- Smoke completo cross-feature en Task 10.
Si dev NO corre, NO lo inicies. Skip.
- Step 6: Commit
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
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
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:
- 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.
- Toggle "Considerar activos" ON: ingresos/deducciones/base gravable suben con la suma de activos del periodo.
- Toggle "Considerar NCs" ON: cambia el bucket — NCs aparecen restando.
- Combinaciones: probar las 4 combinaciones de los 2 toggles + Conciliación on/off (8 total).
- Cross-check
/dashboard: KPIs (ingresos, gastos, utilidad) NO cambian vs antes del deploy. Esto valida que el defaulttrueencalcular*PorRegimenpreserva el dashboard. - 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).
- 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)
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
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
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.
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 -2muestra 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.