# Inmobiliario — CLAUDE.md

> **Estado**: F0 ✅ · F1 ✅ · F2 sin CRM ✅ (adendas, escrituración, entrega, comisiones) · F4 dentro del módulo ✅ (obra) · F5 ✅ (cumplimiento, inversores, fideicomiso). En test, no en prod. Ver `docs/plan-inmobiliario.md`.
> **Spec**: `~/Descargas/Novasis_Inmobiliario_Analisis_Spec_v1.docx` (M110–M124)
> **Análisis de factibilidad y decisiones**: https://claude.ai/artifact/N1MNv7YiPSPwo9WdrWKdAF
> **Rama**: `feat/inmobiliario` (backend desde `pos`, frontend desde `main`). No se mezcla con la rama `crm`.
> **Piloto**: Karmar Inmobiliaria (desarrolladora).
> **Seguimiento (dónde estamos, qué se tocó del ERP existente, entornos)**: `docs/plan-inmobiliario.md`.
> **Regla**: todo cambio a código que NO es de `src/inmobiliario/` (facturas, contabilidad, seguridad, schema de
> modelos existentes, frontend compartido) y todo cambio manual en un entorno se registra en ese plan
> (secciones 4 y 5) en el mismo commit.

## Decisiones (09–10/10/2026, no reabrir sin consultar)

| # | Decisión | Consecuencia en el código |
|---|---|---|
| 1 | **La factura de cada cuota se emite al cobro** | La deuda pendiente vive en el cronograma propio (`inm_operacion_cuotas`, F1), no en `factura_cuotas`. Al cobrar, el vertical arma un `CreateFacturaDto` de contado y lo delega en `FacturasService.create` (mismo patrón que `cobranzas/intereses-moratorios.service.ts`). La mora, las promesas y los cobradores del ERP **no ven** cuotas sin factura: la cartera y la mora de cuotas pendientes las lleva el vertical |
| 2 | El vertical **no depende de CRM F2–F4** para F0/F1/F4/F5 | Sólo F2 (reclamos → tickets CRM-28, portal → CRM-40) y F3 (alquileres → contratos CRM-33/35) los necesitan. WhatsApp (CRM-23/24) es opcional: mientras tanto, PDF y email |
| 3 | Segmento de entrada: **desarrolladora** | Orden después de F1: obra (F4) y postventa (F2) antes que corretaje/alquileres (F3) |
| 4 | Cuotas en **USD fijo o Gs. ajustado por IPC del BCP** | Ver "Moneda y ajuste" |
| 5 | **Base gravada 30 %, IVA 5 %**, como parámetro | `inm_parametros.proporcion_gravada` / `tasa_iva`. Se **copian a la operación al firmar** (F1): cambiar el parámetro no altera contratos firmados |
| 6 | Cobro en pozo = **ingreso al cobrar** | La factura de cuota usa su propio concepto de mapeo contable (`INM_CUOTA_VENTA`, F1). Si el contador lo quiere como anticipo, cambia la cuenta, no el código. Sin asiento de reclasificación en v1 |

## Moneda y ajuste

- **Cuota en USD → factura en USD**, con el tipo de cambio del día del cobro. Si el comprador paga en
  guaraníes, `FacturasService.create` ya lo soporta desde `a54bedc` (`src/facturas/pago-moneda.ts`):
  el pago va a SIFEN en Gs. sin `dTiCamTiPag` y a caja convertido con el TC de la factura.
- **Cuota en Gs. → ajuste por IPC** (`common/ajuste-indice.ts`):
  `cuota × IPC vigente al cobro ÷ IPC del mes de la firma`, redondeado a Gs.
  - "Vigente al cobro" = último índice cargado con período ≤ mes del cobro (`IndicesInmService.vigente`).
  - **Nunca baja del monto original** (deflación → se cobra el original).
  - La factura lleva una sola línea con la cuota ya ajustada.
  - La operación (F1) guarda el **valor** del IPC de la firma, no una FK: así borrar o corregir un
    índice cargado no cambia contratos firmados.
- IVA de la factura de cuota: `src/facturas/calculo-iva.ts` (`calcularLineaFiscal` con
  `GRAVADO_PARCIAL`, `proporcion_iva` de la operación). No rearmar las fórmulas NT 13 a mano.

## Tablas (prefijo `inm_`)

| Tabla | Fase | Notas |
|---|---|---|
| `inm_parametros` | F0 | 1 por empresa; nace sola en la primera lectura (sin paso post-deploy) |
| `inm_indices` | F0 | `(empresa_id, tipo, periodo)` único; `periodo` = día 1 del mes. Lo carga cada empresa |
| `inm_proyectos` | F0 | `codigo` único por empresa e **inmutable**. Si no se elige centro de costo, nace `INM-<codigo>` en `cont_centros_costo` (o se reusa) |
| `inm_etapas`, `inm_hitos` | F0 | Baja lógica. Una etapa con hitos vivos no se da de baja. Cumplir hito registra fecha y usuario: en F1 habilita las cuotas atadas |
| `inm_numeracion` | F0 | OPI-/RES-/COT-. Tabla propia, **no** `crm_numeracion`: el CRM vive en otra rama |

Migraciones: `20261010_inm_base` (F0), `20261011_inm_comercial` (F1), `20261012_inm_postventa` (F2) y `20261013_inm_obra` (F4) y `20261014_inm_inversores_cumplimiento` (F5), en ese orden alfabético. F0 (incluye RLS para el rol del Chat IA,
que la migración `20260804_ai_rls_tenant_isolation` no cubre para tablas nuevas).

## F1 — flujo de venta (backend)

```
unidad DISPONIBLE ─cotizar─► COT- (hasta 3 planes, foto de tramos y cronograma)
   └─reservar─► OPI- RESERVADA (unidad RESERVADA, sólo la cuota de seña)
        ├─ cobrar seña ─► factura contado
        ├─ legajo (origen de fondos, PEP) ─► aprobar
        └─ firmar boleto ─► BOLETO_FIRMADO (unidad VENDIDA, resto del cronograma con base = fecha de firma)
              └─ cobrar cuotas ─► factura por cobro … ─► CANCELADA_TOTAL
   └─desistir (sólo antes del boleto) ─► DESISTIDA (unidad DISPONIBLE)
```

| Pieza | Dónde | Lo que no hay que romper |
|---|---|---|
| Cronograma | `common/cronograma.ts` | Suma exacta al precio en unidades mínimas. Tramos: PORCENTAJE (del precio, total del tramo), MONTO (por cuota), SALDO (uno solo). HITO y POSESION sin fecha. Sin interés de financiación en v1 |
| Bloqueo optimista | `common/inm-utils.ts` `transicionarUnidad` | Toda transición de unidad pasa por acá (updateMany con estado esperado y `version`). Además, índice único parcial: una operación viva por unidad |
| Seña | `cotizaciones.service.ts` `resolverOpcion` | Se valida al cotizar: el plan tiene que tener seña ≥ `pct_sena_min` |
| Descuento | ídem | Sobre `pct_descuento_max` exige `INM_COT_DESCUENTO_AUTORIZAR` (403 si no); queda `descuento_autorizado_por` |
| Comprador facturable | `operaciones.service.ts` `validarFacturable` | Se valida al reservar con `PersonasService.validatePersonaById` (mismas reglas que FacturasService) |
| Boleto | `operaciones/boleto.ts` | 422 sin legajo aprobado, sin seña cobrada o con titulares que no suman 100 %. Plantilla HTML con `{{variables}}` en `inm_parametros.plantilla_boleto`; el texto por defecto es un MODELO a revisar por la escribanía. Se guarda la foto (`boleto_html`) |
| Cobro | `cobros/cobros.service.ts` | Una factura de contado por cobro, una línea por cuota, afectación 4. USD: factura en USD con la última cotización de venta; pago en Gs. o USD. Gs. con IPC: se factura la cuota ajustada, el cronograma descuenta el capital y `inm_cobro_cuotas.monto_ajuste` guarda la diferencia. Cobros de una misma operación serializados en memoria (prod corre en fork) |
| Contabilidad | `contabilidad/services/integracion.service.ts` | Si la factura tiene un `inm_cobros`, ventas van a `INM_VENTAS_5` / `INM_VENTAS_EXENTAS` (con respaldo en `VENTAS_5` / `VENTAS_EXENTAS`) y al centro de costo del proyecto |
| Cartera y avisos | `cartera/` | Estado de cuenta en `common/estado-cuenta.ts`. Cron 05:30 (America/Asuncion): vence cotizaciones; libera reservas vencidas **sin** seña cobrada; alerta reservas por vencer/vencidas con seña, rescisión (cuotas vencidas ≥ umbral) y lotes con el % que habilita la escritura |

Pendiente de F1, explícitamente fuera de v1: interés de financiación, anulación de un cobro (hoy: anular la
factura a mano), facturar la mora (hoy se estima y se muestra), plano con zonas clicables.

## F2 — cierre del ciclo (sin CRM)

| Pieza | Dónde | Lo que no hay que romper |
|---|---|---|
| Adendas | `adendas/` | Se crean PENDIENTES con su `resumen` y se aplican al aprobar, **recalculando** (la venta pudo cambiar). Una sola pendiente por operación (índice único parcial + 409 previo). Nunca se borran cuotas: pendientes → ANULADA, parciales → se cierran en lo cobrado (PAGADA), nuevas con `adenda_id`. La rescisión exige además `INM_OPE_RESCISION_APROBAR` y deja la unidad DISPONIBLE |
| Cambio de unidad | ídem | La unidad nueva tiene que estar DISPONIBLE y en la misma moneda; si lo cobrado supera el precio nuevo, no es un cambio de unidad sino una devolución |
| Escritura | `postventa/` | Checklist por defecto editable. Con saldo exige hipoteca (el monto por defecto es el saldo). Escritura y entrega van en cualquier orden: ENTREGADA gana |
| Entrega | ídem | Las cuotas POSESION sin fecha pasan a vencer el día de la entrega; garantía = fecha + `meses_garantia` |
| Comisiones | `comisiones/` | `devengar` es idempotente (único por operación + esquema + hito) y se llama tras reserva, boleto, cobro, escritura, entrega, adenda y en la tarea diaria. Los hitos no alcanzados nunca se devengan; al rescindir/desistir se revierten las devengadas no liquidadas (si el esquema lo pide). Liquidación: manual con referencia (RRHH y CxP fuera de esta fase) |

## F4 — obra (dentro del módulo)

Todo en la **moneda del proyecto y sin IVA** (el IVA de compras es crédito fiscal, no costo).

| Pieza | Dónde | Lo que no hay que romper |
|---|---|---|
| Presupuesto | `obra/presupuesto-obra.service.ts` | Partidas (`inm_partidas`) por proyecto, código único, rubro libre, etapa opcional. Baja lógica y sólo sin imputaciones ni certificados vivos. Carga por lote (pegar de planilla). Avance físico ponderado por presupuesto; `desvio = ejecutado − presupuesto × avance` |
| Certificados | `obra/certificados-obra.service.ts` | CER-. BORRADOR → APROBADO → (ANULADO). Aprobar imputa el **bruto** (el fondo de reparo también es costo) y sube el avance de la partida si el del certificado es mayor. El avance de un certificado es **acumulado**: no puede ser menor al último aprobado del mismo contratista en la partida. Anular quita la imputación pero **no** baja el avance (se avisa). Fondo de reparo por defecto en `inm_parametros.pct_fondo_reparo` (5 %) |
| Factura del contratista | ídem `vincularFactura` | Se carga en Compras y se vincula al certificado (mismo proveedor, no anulada, no imputada aparte). Una compra vinculada **no** se imputa: su costo ya entra por el certificado. El pago va por Tesorería como cualquier compra |
| Imputación | `obra/costos-obra.service.ts` | `inm_imputaciones`: COMPRA / GASTO / CERTIFICADO / MANUAL. Compras y gastos se **leen** (`compra_cab`, `gasto_cab`), nunca se escriben. Base = `total − IVA`. Un comprobante se puede repartir entre partidas y proyectos; el control de "no pasarse" va en la moneda del comprobante (`monto_documento`) con `pg_advisory_xact_lock` por comprobante. Conversión: cotización del comprobante si es extranjero y la trae; si no, `getCotizacionVigente` a su fecha (respaldo 1 → 422) |
| Costo por unidad | `presupuesto-obra.service.ts` `costoPorUnidad` | Prorrateo por superficie (propia + común; si no hay, terreno) con resto mayor: la suma da exacto. Partidas de una etapa sólo entre las unidades de esa etapa. Margen sólo si precio y costo están en la misma moneda |

Fuera de F4 (tocan Compras/Gastos/Tesorería, ver plan sección 8): proyecto y partida al cargar el comprobante,
cuenta por pagar desde el certificado, asiento con centro de costo, desimputar al anular una compra.

## F5 — cumplimiento, inversores y fideicomiso

| Pieza | Dónde | Lo que no hay que romper |
|---|---|---|
| Legajo del cliente | `cumplimiento/legajos-cliente.service.ts` | Uno por cliente y empresa (`inm_legajos`), reutilizable en todas sus compras; distinto del legajo de la OPERACIÓN (F1, origen de fondos de esa compra). Reglas puras en `cumplimiento/cumplimiento-reglas.ts`: riesgo calculado (PEP o beneficiario PEP o ROS previo → ALTO; jurídica o inusual abierta → MEDIO); el pedido a mano se guarda aparte (`nivel_riesgo_manual`, null lo quita) y el final es el mayor de los dos; registrar un ROS sube a ALTO el legajo del cliente; anular un documento requerido de un aprobado lo devuelve a PENDIENTE; para aprobar: actividad, origen de fondos, cédula o RUC, y si es jurídica beneficiario final ≥ 10 % y estatuto; PEP con declaración jurada. Vigencia 24 meses (12 si ALTO). Cambiar un dato de fondo de un aprobado lo vuelve a PENDIENTE. **Nada se borra** (archivo 5 años): documentos se anulan |
| Boleto | `operaciones.service.ts` `firmarBoleto` | Si `exigir_legajo_cliente` (default true), 422 si algún comprador o cotitular no tiene legajo aprobado y vigente (`impedimentosBoleto`). El detalle de la operación trae `debida_diligencia` para mostrarlo sin permiso de cumplimiento |
| Documentos | `cumplimiento/inm-storage.service.ts` | Spaces (mismo bucket y variables que RRHH), ruta `inmobiliario/{empresa}/legajos/{cliente}/…`, privados, URL firmada de 10 min. PDF/JPG/PNG/WEBP hasta 10 MB |
| Operaciones inusuales | `cumplimiento/inusuales.service.ts` | INU-. Automáticas idempotentes por `clave`: `EFE:<cobro>` (cobro en efectivo ≥ `umbral_efectivo_gs`, evaluado tras cada cobro sin frenarlo), `FRA:<cliente>:<día>` (tarea diaria: efectivo fraccionado en `dias_fraccionamiento`), `PEP:<legajo>` (al aprobar un PEP). Manuales con cliente u operación. DESCARTADA/REPORTADA son finales, exigen análisis; REPORTADA exige número y fecha del ROS. El cobro guarda `medio_pago_codigo` y `monto_gs` (desde F5) |
| Inversores | `inversores/inversores.service.ts` | Participación por proyecto (suma ≤ 100 %). Aportes DINERO/TERRENO/SERVICIOS/OTRO (se anulan, no se borran). Canje: la unidad pasa a BLOQUEADA con motivo "Canje con el inversor …" (`transicionarUnidad`); anular la devuelve a DISPONIBLE si sigue bloqueada por el canje. Distribución: cada inversor recibe su % del monto (resto mayor), lo no cubierto es de la desarrolladora; BORRADOR → APROBADA → pagos por línea con referencia; no se anula con pagos. Sin cascadas de retorno (fuera de v1) |
| Fideicomiso | `fideicomiso/fideicomiso.service.ts` | Informe por período: ventas, cobros y aportes, costos de obra por rubro y distribuciones pagadas, avance (de F4) e hitos, cartera, inversores. BORRADOR se recalcula; EMITIDO se recalcula una última vez y queda congelado (409 al regenerar). Cron día 1 06:00: borrador del mes anterior para proyectos con `titular_tipo = FIDEICOMISO` |

Fuera de F5 (ver plan sección 8): pagos a inversores y aportes como movimientos de Tesorería, presentación del ROS
(se hace en el sistema de la SEPRELAD), IA del vertical.

## Permisos

Módulo `INMOBILIARIO` en `src/seguridad/seeds/seguridad.seed-data.ts` (se siembra solo al arrancar).

| Submódulo | Privilegios |
|---|---|
| `INM_CONFIG` | `INM_CFG_PARAMETROS_VER/EDITAR`, `INM_CFG_INDICE_VER/CREAR/EDITAR/ELIMINAR` |
| `INM_PROYECTOS` | `INM_PRO_PROYECTO_VER/CREAR/EDITAR/ELIMINAR`, `INM_PRO_HITO_CONFIRMAR`, `INM_PRO_HITO_REVERTIR`, `INM_PRO_PLAN_VER/CREAR/EDITAR/ELIMINAR` |
| `INM_UNIDADES` | `INM_UNI_UNIDAD_VER/CREAR/EDITAR/ELIMINAR/IMPORTAR/BLOQUEAR`, `INM_UNI_PRECIO_EDITAR` |
| `INM_COTIZACIONES` | `INM_COT_COTIZACION_VER/CREAR/ANULAR`, `INM_COT_DESCUENTO_AUTORIZAR` |
| `INM_OPERACIONES` | `INM_OPE_OPERACION_VER/EDITAR/ANULAR`, `INM_OPE_RESERVA_CREAR/PRORROGAR`, `INM_OPE_LEGAJO_APROBAR`, `INM_OPE_BOLETO_CONFIRMAR` |
| `INM_COBRANZA` | `INM_COB_CARTERA_VER`, `INM_COB_COBRO_CREAR` |
| `INM_OBRA` (F4) | `INM_OBR_PRESUPUESTO_VER/CREAR/EDITAR/ELIMINAR`, `INM_OBR_CERTIFICADO_VER/CREAR/APROBAR/ANULAR`, `INM_OBR_COSTO_VER/IMPUTAR/ELIMINAR` |
| `INM_CUMPLIMIENTO` (F5) | `INM_CUM_LEGAJO_VER/EDITAR/APROBAR`, `INM_CUM_INUSUAL_VER/CREAR/GESTIONAR` |
| `INM_INVERSORES` (F5) | `INM_INV_INVERSOR_VER/CREAR/EDITAR`, `INM_INV_APORTE_CREAR/ANULAR`, `INM_INV_DISTRIBUCION_CREAR/APROBAR/PAGAR`, `INM_INV_FIDEICOMISO_VER/GENERAR/EMITIR` |

Perfiles de sistema nuevos: **Comercial inmobiliario**, **Cobranza inmobiliaria**, **Obra inmobiliaria** y **Oficial de cumplimiento**.

Controller: `@UseGuards(AuthGuard('jwt'), SubmoduloGuard)` + `@RequireSubmodulo(...)` en la clase y
`@RequirePermission('INMOBILIARIO', ...)` por endpoint, igual que el CRM.

## Endpoints (`/api/v1/inmobiliario/...`)

`GET/PATCH config/parametros` · `GET/POST config/indices` · `PATCH/DELETE config/indices/:id` ·
`GET/POST proyectos` · `GET/PATCH/DELETE proyectos/:id` · `POST proyectos/:id/etapas` ·
`PATCH/DELETE etapas/:id` · `POST proyectos/:id/hitos` · `PATCH/DELETE hitos/:id` ·
`POST hitos/:id/cumplir` · `POST hitos/:id/revertir` · `GET config/boleto/plantilla`

F1: `GET/POST unidades` · `POST unidades/importar` · `GET/PATCH/DELETE unidades/:id` · `POST unidades/:id/{precio,publicar,bloquear,desbloquear}` ·
`GET/POST planes` · `POST planes/simular` · `GET/PATCH/DELETE planes/:id` · `GET/POST cotizaciones` · `GET cotizaciones/:id` ·
`POST cotizaciones/:id/anular` · `GET operaciones` · `POST operaciones/reservar` · `GET operaciones/:id` ·
`POST operaciones/:id/{prorrogar,desistir}` · `POST/PATCH/DELETE operaciones/:id/titulares[/:titularId]` ·
`PATCH operaciones/:id/legajo` · `POST operaciones/:id/legajo/{aprobar,observar}` · `GET operaciones/:id/boleto[/preview]` ·
`POST operaciones/:id/boleto` · `PATCH operaciones/:id/cuotas/:cuotaId` · `POST operaciones/:id/cobros[/preview]` · `GET cartera`

F4 (`obra/...`): `GET proyectos/:id/{presupuesto,partidas,costo-unidades,imputables}` · `GET certificados/:id/facturas` · `POST proyectos/:id/partidas[/lote]` ·
`PATCH/DELETE partidas/:id` · `GET/POST certificados` · `GET/PATCH certificados/:id` ·
`POST certificados/:id/{aprobar,anular,devolver-fondo}` · `PUT certificados/:id/factura` · `GET/POST imputaciones` · `DELETE imputaciones/:id`

F5 (`cumplimiento/...`): `GET resumen` · `GET legajos` · `GET/PUT legajos/cliente/:clienteId` · `POST legajos/cliente/:clienteId/{aprobar,observar,documentos}` ·
`GET legajos/documentos/:id/url` · `POST legajos/documentos/:id/anular` · `GET/POST inusuales` · `GET inusuales/:id` · `POST inusuales/:id/gestionar`.
F5 (raíz): `GET proyectos/:id/{inversores,distribuciones,informes-fideicomiso}` · `GET/PATCH/DELETE inversores/:id` · `POST inversores` ·
`POST inversores/{aportes,canjes}` · `POST inversores/{aportes,canjes}/:id/anular` · `POST distribuciones` · `POST distribuciones/:id/{aprobar,anular}` ·
`POST distribuciones/lineas/:id/pagar` · `POST proyectos/:id/informes-fideicomiso` · `GET informes-fideicomiso/:id` · `POST informes-fideicomiso/:id/emitir`

## Reglas — NO hacer

1. No tocar `FacturasService.create` para el vertical ni volver nullable `factura_cuotas.factura_cab_id`.
2. No crear facturas a crédito por la venta entera al firmar (decisión 1).
3. No leer `proporcion_gravada` de `inm_parametros` al facturar una cuota: va la copiada en la operación.
4. No importar nada de `src/crm` hasta que la fase que lo necesita esté planificada (decisión 2).
5. No borrar físicamente proyectos, etapas ni hitos.

## Frontend (repo `novasispy-erp`, rama `feat/inmobiliario`)

| | |
|---|---|
| Servicio | `src/api/inmobiliario.service.js` |
| Pantallas | `src/pages/inmobiliario/` — `InmProyectos`, `InmProyectoDetalle`, `InmConfiguracion` |
| Diálogos | `src/components/organismos/inmobiliario/` |
| Enums | `src/components/_standards/enums/inmobiliario.js` |
| Unidades y planes | pestañas de `InmProyectoDetalle` (`?tab=unidades` / `?tab=planes`) + `organismos/inmobiliario/{unidades,planes}/` |
| Cotizador | `InmCotizaciones`, `InmCotizacionNueva` (`?unidad_id=`), `InmCotizacionDetalle` (impresión A4 y reserva) + `organismos/inmobiliario/cotizaciones/` |
| Ventas y cobro | `InmOperaciones`, `InmOperacionDetalle` (qué falta para el boleto, legajo, boleto, cronograma, `CobroDialog`) + `organismos/inmobiliario/operaciones/` |
| Cartera | `InmCartera` |
| Configuración | `InmConfiguracion`: Parámetros · Facturación y ventas (producto y timbrado de la factura de cuotas) · Boleto (plantilla) · IPC |

Clientes del ERP: siempre `searchClientes` (`GET /clientes/search`), que devuelve ids de la tabla `clientes`.
La pantalla de clientes del ERP lee de Supabase y sus ids son otros.

El serializador global (`BigIntSerializerInterceptor`) reemplaza por `[Circular]` un objeto que aparece dos veces
en la misma respuesta. Por eso el backend devuelve una copia del cliente en cada uso (`clienteDe`).
| Menú | `utils/dataEstatica.jsx` + `hooks/useMenuAcceso.js` (`MODULO_RAIZ_META.INMOBILIARIO`) + `routers/routes.jsx` + `utils/screenSubmoduloMap.js` |

El layout ya dibuja el breadcrumb a partir de `screenSubmoduloMap.js`: no agregues `<Breadcrumbs>` en
la pantalla (sale duplicado). Para volver al listado, botón "Volver a proyectos".

## Pruebas

**Regla (pedido del usuario, 10/10/2026): cada fase se cierra con su suite e2e de Playwright** en el repo del
frontend (`e2e/inmobiliario-fN.real.spec.js`), contra un entorno real, que recorra la fase por la pantalla. No
se da una fase por terminada sin ella.

```bash
# frontend
E2E_BASE_URL=https://facerp.factupy.es E2E_API_URL=https://facerp-api.factupy.es/api/v1 \
E2E_USER=… E2E_PASS=… E2E_INM_CLIENTE="…" pnpm run e2e:inm
# contra local: E2E_BASE_URL=http://localhost:5181 E2E_API_URL=http://localhost:3099/api/v1 E2E_TOKEN=<jwt>
```
La de F1 emite facturas reales en el entorno: nunca contra producción.

```bash
npx jest src/inmobiliario src/facturas/calculo-iva.spec.ts src/facturas/pago-moneda.spec.ts
```

Prueba manual contra la base local: las suscripciones locales están vencidas; la única activa es
**Derlis Empresa Holding**, que además pasa el control de plan por ser holding. Levantar el backend
en otro puerto (`PORT=3099 node dist/main`) y el frontend con
`VITE_API_BASE_URL=http://localhost:3099/api/v1 vite --port 5181`. Ojo: el backend local corre los
crons (sync SIFEN contra el middleware de **test**); apagarlo al terminar.
