feat(reportes): rediseño Estado de Resultados vertical con drill-down, análisis horizontal/vertical y export Excel
- Nuevo endpoint GET /reportes/estado-resultados-detallado con cálculo contable:
* Ventas, Devoluciones, Ventas netas, Costo de ventas, Utilidad bruta,
Gastos operativos, Utilidad de la operación
* Fórmula: subtotal_mxn - descuento_mxn (sin impuestos), nómina usa total_mxn
* Excluye anticipos (uso_cfdi=P01 o clave_prod_serv=84111506)
* Filtro por régimen fiscal opcional
* Año anterior calculado automáticamente
- Nuevo endpoint GET /reportes/estado-resultados/drill-down:
* Nivel 1: resumen agrupado por RFC
* Nivel 2: CFDIs individuales filtrados por categoría
* Categorías: ventas, devoluciones, costo-ventas, gastos-operativos
- Nuevo endpoint GET /reportes/estado-resultados/export:
* Genera Excel con formato condicional (verde/rojo, negritas)
- Frontend:
* Tabla vertical con % vertical, año anterior y variación %
* Filas clickeables para drill-down modal de 2 niveles
* Top 10 Clientes/Proveedores mantenidos debajo
* Selector de régimen conectado al reporte
- Fix: NaN en total de drill-down nivel 2 por numeric como string en pg
* Agregado ::float en queries SQL de CFDIs individuales
This commit is contained in:
@@ -337,3 +337,56 @@ El usuario esperaba verlo en abril porque el pago ocurrió en abril, pero el sis
|
||||
- El endpoint `POST /emitir-factura-pago/:paymentId` requiere rol `platform_admin`
|
||||
- La regla "primer pago no se factura automáticamente" sigue vigente; los subsecuentes sí son automáticos
|
||||
- Los CFDIs importados por SAT sync ahora se asocian correctamente al `contribuyente_id` correspondiente
|
||||
|
||||
---
|
||||
|
||||
## 12. Rediseño del Estado de Resultados en `/reportes`
|
||||
|
||||
**Fecha:** 4 de mayo de 2026
|
||||
|
||||
### Problema
|
||||
El tab "Estado de Resultados" mostraba solo 4 KPI cards y dos listas con títulos engañosos (decían "Cliente/Proveedor" pero mostraban regímenes fiscales). No había análisis horizontal, vertical, ni drill-down.
|
||||
|
||||
### Solución
|
||||
Se reemplazó por un estado de resultados vertical contable con 7 líneas, análisis comparativo vs año anterior, análisis vertical (% de ventas), drill-down por RFC → CFDI, exportación a Excel y filtro por régimen fiscal.
|
||||
|
||||
### Backend (`apps/api/`)
|
||||
|
||||
**Nuevos archivos/modificaciones:**
|
||||
|
||||
| Archivo | Cambio |
|
||||
|---|---|
|
||||
| `src/services/reportes.service.ts` | **Nuevas funciones:** `getEstadoResultadosDetallado`, `getEstadoResultadosDrillDown`, `exportEstadoResultadosToExcel` |
|
||||
| `src/controllers/reportes.controller.ts` | **3 nuevos handlers:** `getEstadoResultadosDetallado`, `getEstadoResultadosDrillDown`, `exportEstadoResultados` |
|
||||
| `src/routes/reportes.routes.ts` | Registradas 3 rutas nuevas |
|
||||
| `packages/shared/src/types/reportes.ts` | **Nuevo tipo:** `EstadoResultadosDetallado` |
|
||||
|
||||
**Endpoints nuevos:**
|
||||
- `GET /reportes/estado-resultados-detallado` — Tabla vertical con año anterior
|
||||
- `GET /reportes/estado-resultados/drill-down?categoria=X&rfc=Y` — Resumen por RFC o CFDIs individuales
|
||||
- `GET /reportes/estado-resultados/export` — Descarga Excel con formato condicional
|
||||
|
||||
**Lógica de cálculo:**
|
||||
| Línea | Fórmula | Filtros |
|
||||
|---|---|---|
|
||||
| Ventas | `subtotal_mxn - descuento_mxn` | Emitidas tipo I, PUE/PPD, vigentes, excluyendo anticipos (`uso_cfdi != 'P01'` ni concepto `84111506`) |
|
||||
| Devoluciones | `subtotal_mxn - descuento_mxn` | Emitidas tipo E, relación `01` o `03`, vigentes |
|
||||
| Costo de ventas | `subtotal_mxn - descuento_mxn` | Recibidas tipo I, PUE/PPD, `uso_cfdi = 'G01'`, vigentes |
|
||||
| Gastos operativos | `subtotal_mxn - descuento_mxn` (recibidos) + `total_mxn` (nómina) | Recibidas tipo I excluyendo G01 + Emitidas tipo N, vigentes |
|
||||
| Totales | Calculados | Ventas netas, Utilidad bruta, Utilidad de la operación |
|
||||
|
||||
### Frontend (`apps/web/`)
|
||||
|
||||
| Archivo | Cambio |
|
||||
|---|---|
|
||||
| `app/(dashboard)/reportes/components/estado-resultados-table.tsx` | **Nuevo** — Tabla vertical con concepto, monto, % vertical, año anterior, variación % |
|
||||
| `app/(dashboard)/reportes/components/drill-down-modal.tsx` | **Nuevo** — Modal de dos niveles: RFC resumen → CFDIs individuales |
|
||||
| `lib/api/reportes.ts` | Agregados wrappers para los 3 endpoints nuevos |
|
||||
| `lib/hooks/use-reportes.ts` | Agregados `useEstadoResultadosDetallado` y `useEstadoResultadosDrillDown` |
|
||||
| `app/(dashboard)/reportes/page.tsx` | Integrada tabla nueva; conectado `RegimenSelector` al reporte; mantenidos Top 10 Clientes/Proveedores debajo |
|
||||
|
||||
### Fix posterior: Total NaN en drill-down nivel 2
|
||||
|
||||
**Causa:** PostgreSQL devolvía `numeric` como string en el driver `pg`. Al sumar strings en el `reduce` del frontend, JavaScript concatenaba en lugar de sumar, generando `NaN` al formatear.
|
||||
|
||||
**Fix:** Se agregó `::float` en las 5 queries SQL de CFDIs individuales del drill-down, forzando que el backend devuelva números reales.
|
||||
|
||||
Reference in New Issue
Block a user