Sistema de Rendiciones de Gastos

Documentación de negocio y de cómo están armados los flujos: carga del gasto, aprobación multinivel, firma hacia nómina, formularios custom por empresa (circuito Viáticos APSV), notificaciones, reportes Excel, multiempresa, import de personas e IA opcional.

Proyecto: demoRendiciones Versión doc: 1.2.0 Actualizado: 2026-08-21 Stack: Node · Express · Sequelize · MSSQL Contrato: /api-docs (OpenAPI 1.2.0) · /docs · API.md DER: docs/schema.dbml Índice: docs/ENTREGA.md

1. Resumen ejecutivo

El sistema permite a empleados de una empresa (o de sus subempresas) cargar rendiciones de gastos con fecha, monto, observaciones y comprobantes; pasarlas por un workflow de aprobación configurable de N niveles; listar el proceso completo; exportar a Excel; e integrar el resultado con nómina u otros sistemas vía la misma API REST.

Principio rector: API-first / todo en services. El front propio, un ERP o un portal externo consumen /api/v1/*. La lógica de estados, tenant y workflow vive solo en el backend (*Service), nunca duplicada en la UI.

Capacidades Must

  1. Carga de rendición (fecha, observaciones, monto, adjuntos imagen/PDF).
  2. Workflow con N niveles configurables y avance secuencial.
  3. Listados con cabecera + adjuntos + historial por nivel.
  4. Exportación Excel del listado (mismo alcance de filtros).
  5. Multiempresa nativo + subempresas (árbol parentId).
  6. API-first / todo en services; mismo contrato para front e integradores.
  7. Importación de personas (API + front), scoped al tenant.
  8. IA externa opcional (análisis de comprobante → sugerencia o alta vía services).
  9. Seed demo reproducible (database/seeds/), incl. empresa APSV.
  10. Formularios custom por empresa (standard | viaticos_apsv).
  11. Circuito Viáticos APSV: borrador → aprobación → rendición → liquidación (estados 4–10).
  12. Notificaciones in-app + email SMTP opcional (NotificationService).
  13. Reportes Excel §11 (ReportService), distintos del export de listado.

2. Negocio: qué problema resuelve

En operaciones (sucursales, fuerza de campo, admin), las personas gastan y necesitan rendir el gasto con evidencia. Sin un proceso claro aparecen demoras, rechazos opacos y datos que no llegan a nómina. Este producto estandariza:

KPIs de producto (referencia)

KPIPara qué sirve
Tiempo ciclo de aprobaciónDesde create hasta último nivel OK
% rechazo por nivelCalidad de comprobantes / reglas
Éxito de import personasOnboarding multiempresa
Latencia p95 create/listSalud API
Uso de export ExcelAdopción de control / auditoría

3. Roles y capacidades

RolCódigoPuedeNo puede
Administrador admin Empresas/subempresas de su rama; import personas; plantillas workflow; ver/gestionar según alcance; aprobar como override; firmar Actuar fuera de scopeCompanyIds
Empleado employee Crear / editar / cancelar / firmar propias; listar propias; export propias; usar IA suggest Aprobar niveles ajenos; ver rendiciones de otros (salvo admin)
Aprobador approver Bandeja del nivel activo asignado a su userId; aprobar/rechazar con motivo Decidir un nivel que no es el activo o no es suyo
Integración API Key Misma API con alcance de empresa (+ hijos si inheritChildren) Cruzar a otra raíz de holding
Los permisos se validan siempre en backend (authenticate + requireRoles + checks en services). El front solo oculta botones; no es la fuente de verdad.

Mapeo roles APSV (documento → sistema)

Rol documentoRol sistemaUsuario seed
Solicitanteemployeesolicitante@apsv.demo
Jefe Directo (L1)approverjefe@apsv.demo
Gerente (L2)approvergerente@apsv.demo
Administración / Rendidor (L3)adminadmin@apsv.demo

4. Multiempresa y subempresas

Company (raíz / holding)          parentId = NULL
 ├── Subcompany A                 parentId = raíz
 │    └── Subcompany A.1          parentId = A
 └── Subcompany B                 parentId = raíz
ConceptoRegla
companies.idPK de empresa o subempresa
companies.parentIdNULL = raíz; FK al padre; sin ciclos (validado en CompanyService)
Entidades operativasSiempre con companyId (rendiciones, adjuntos, workflows, users, API keys, logs IA)
Visibilidad admin raízPuede ver/gestionar descendientes (scopeCompanyIds)
Visibilidad admin hojaSolo su rama
API KeyLigada a una empresa; opcionalmente hereda hijos

API empresas

MétodoRutaAcción
GET/api/v1/companiesListar según alcance del token
GET/api/v1/companies/:idDetalle
GET/api/v1/companies/:id/treeSubárbol
POST/api/v1/companiesCrear (admin; parentId opcional)
PATCH/api/v1/companies/:idActualizar

5. Máquina de estados

5.1 Estados de la rendición

IdCódigo APIEtiqueta UISignificado
0 DISMISSED Desestimada Cancelada por el empleado o rechazada en algún nivel del workflow
1 PENDING Pendiente En proceso de aprobación (hay niveles pendientes)
2 PENDING_PAYMENT Pendiente de pago Todos los niveles OK (y/o firmada); lista para nómina/pago
3 PAID Pagada Cierre desde nómina / callback (estado final de pago)
4DRAFTBorradorSolo formCode viaticos_apsv — sin workflow
5SUBMITTEDEnviadaTransición breve opcional
6APPROVEDAprobadaWorkflow completo APSV
7PENDING_SETTLEMENTPendiente de rendiciónLista para planilla etapa 2
8SETTLEDRendidaViáticos/gastos cargados
9OBSERVEDObservadaAdmin pide corrección
10LIQUIDATEDLiquidadaAdmin marca liquidada

Estados 0–3 son el circuito formCode=standard (sin regresión). Estados 4–10 solo aplican a formCode=viaticos_apsv; las empresas standard no los usan en el flujo diario.

Formularios custom: cada empresa declara su formulario en company_form_configs (formCode + schemaJson + viaticoRulesJson) vía GET/PUT /companies/:id/form-config. El front renderiza y valida según ese schema; la lógica de transiciones vive en ReimbursementService según formCode.

Diagrama standard (0–3)

                    ┌──────────────┐
   create ─────────►│   PENDING    │
                    └──────┬───────┘
           reject/cancel   │    último nivel APPROVED
                │          │    (+ firma / externalId local)
                ▼          ▼
         ┌──────────┐  ┌──────────────────┐
         │ DISMISSED│  │ PENDING_PAYMENT  │──► PAID
         └──────────┘  └──────────────────┘
                              ▲
                         /signature
                    (niveles todos APPROVED)

Diagrama APSV (4–10)

DRAFT ──submit──► SUBMITTED (+ workflow N niveles)
                      │
           reject ────┼──► DISMISSED
                      │
              all approve ──► APPROVED ──► PENDING_SETTLEMENT
                                              │
                                    settlement ┼──► SETTLED
                                              │         │
                                         observe        liquidate
                                              ▼         ▼
                                          OBSERVED   LIQUIDATED
                                              │
                                    re-settlement ──► SETTLED

En la respuesta JSON cada estado viene como objeto { id, name, label } (name en inglés, label en español) para no acoplar la UI a códigos mágicos.

5.2 Estados de cada nivel (workflow_details)

IdCódigoEtiqueta
0REJECTEDRechazado
1PENDINGPendiente
3APPROVEDAprobado

El nivel activo es el de menor level que sigue en PENDING. Solo ese nivel puede decidir. Tras aprobarlo, se desbloquea el siguiente.

6. Circuito Viáticos APSV

Empresa demo APSV (id=4) con formCode=viaticos_apsv. Schema: database/seeds/forms/viaticos_apsv.schema.json. Holding / Sur / Norte siguen en standard sin regresión. IA off por defecto en APSV (aiScanEnabled=false).

6.1 Wizard UI en 2 etapas

  1. Solicitud / planilla de liquidación de viáticos — destino, motivo, fechas, agentes, vehículo, cuotas; se guarda como DRAFT y se confirma con POST .../submit.
  2. Rendición de viáticos + gastos — tras aprobación completa (PENDING_SETTLEMENT): líneas de viáticos/gastos, adjuntos, envío con POST .../settlement.

6.2 Transiciones (services)

AcciónEndpointDesdeHaciaQuién
Crear borradorPOST /reimbursementsDRAFTSolicitante
Editar borradorPATCH /reimbursements/:id o updateDRAFT / OBSERVED*mismoDueño
ConfirmarPOST .../submitDRAFTSUBMITTED + workflowDueño
Aprobar/rechazar nivelPOST .../approveSUBMITTEDsiguiente / DISMISSED / APPROVED→PENDING_SETTLEMENTAprobador del nivel
Cargar rendiciónPOST .../settlementPENDING_SETTLEMENT u OBSERVEDSETTLEDSolicitante / admin
ObservarPOST .../observeSETTLEDOBSERVEDAdmin / Administración
LiquidarPOST .../liquidateSETTLEDLIQUIDATEDAdmin autorizado
HistorialGET .../eventsScope tenant
PDF planillasGET .../pdfScope + ownership

* En OBSERVED se reenvía settlement tras corrección; DRAFT es editable/cancelable sin workflow activo.

6.3 Cadena de aprobación seed

Solicitante → Jefe Directo (L1) → Gerente (L2) → Administración (L3) → PENDING_SETTLEMENT

6.4 Services de dominio APSV

ServiceResponsabilidad
FormConfigServiceCRUD / resolver company_form_configs por companyId
ViaticoCalculationServiceCálculo de importes según viaticoRulesJson
ReimbursementServicedraft / submit / approve / settlement / observe / liquidate; branch por formCode
NotificationServiceIn-app + SMTP; fire-and-forget; no aborta la TX de negocio
ReportServiceReportes Excel §11 scoped por tenant
PdfExportServicePDF planillas (liquidación / viáticos-gastos / comprobante standard)

6.5 Invariantes APSV

  1. Empresas standard: endpoints settlement/observe/liquidate rechazan si formCode ≠ viaticos_apsv.
  2. DRAFT: sin workflow activo; editable/cancelable por el dueño.
  3. Submit crea workflow; rechazo de nivel → DISMISSED.
  4. Settlement solo desde PENDING_SETTLEMENT u OBSERVED.
  5. Liquidate solo desde SETTLED (rol autorizado).
  6. Notificaciones y reportes scoped por tenant; sin cruzar empresas.
  7. Tras mutaciones relevantes: reimbursement_events + NotificationService.notify.

7. Flujo extremo a extremo — formCode standard

1. Admin configura empresa/subempresa + plantilla workflow (niveles + aprobadores)
2. Admin importa / da de alta personas (employee, approver) en la empresa destino
3. Empleado inicia sesión → Nueva rendición
      · Fecha del gasto (obligatoria)
      · Monto (obligatorio)
      · Observaciones (obligatorias)
      · ≥1 adjunto (imagen o documento allowlist)
   Opcional: asistente IA (suggest) → confirma → create
4. Sistema crea reimbursement PENDING + adjuntos + workflow con N details PENDING
5. Aprobador L1 ve bandeja (inbox) → aprueba o rechaza (+ motive si rechazo)
6. Si rechazo → rendición DISMISSED; niveles pendientes restantes se cancelan
7. Si aprueba → desbloquea L2 … Ln
8. Último nivel aprobado → PENDING_PAYMENT (+ signature/externalId locales si aplica)
9. Empleado/admin puede firmar explícitamente (/signature) si aún PENDING con niveles OK
10. Nómina / integración marca PAID (callback) — no puede volver a PENDING
11. Auditoría / control: listado proceso completo + export Excel

8. Flujos por rol (UX)

Empleado

  1. Login
  2. Listado de mis rendiciones (filtros fecha/estado)
  3. Nueva (manual) o Asistida IA
  4. Detalle: adjuntos + niveles
  5. Editar / cancelar solo si PENDING y ningún nivel revisado
  6. Firmar cuando todos los niveles APPROVED
  7. Export Excel de su alcance

Aprobador

  1. Login
  2. Bandeja “Aprobar” (inbox=1: solo nivel activo = yo)
  3. Abrir detalle + comprobantes
  4. Aprobar o Rechazar (motive obligatorio en rechazo)
  5. La rendición avanza o queda DISMISSED

Administrador

  1. Árbol de empresas / crear subempresa
  2. Import personas (JSON, CSV/XLSX) o alta unitaria
  3. CRUD plantillas de workflow (niveles → aprobadorUserId)
  4. Form-config por empresa (standard / viaticos_apsv)
  5. Visibilidad de rendiciones de su rama; override de aprobación si aplica
  6. APSV: observar / liquidar; reportes §11; bandeja de notificaciones

Solicitante APSV

  1. Wizard etapa 1 → guardar DRAFT → confirmar (submit)
  2. Seguir aprobaciones Jefe → Gerente → Administración
  3. Wizard etapa 2 (PENDING_SETTLEMENT / OBSERVED) → settlement
  4. Descargar PDF de planillas; ver campana de notificaciones

Navegación del front: Rendiciones · Aprobar · Empresas · Personas · Plantillas · Reportes · Campana · Salir, con selector de empresa/rama. La UI se adapta al formCode de la empresa activa.

9. Workflow multinivel — cómo está armado

9.1 Piezas

PiezaTabla / conceptoRol
Plantilla workflow_templates + workflow_template_levels Define, por empresa, cuántos niveles y qué aprobador va en cada uno. Debe haber una activa al crear.
Instancia workflows 1:1 con la rendición; copia el estado global del proceso
Detalle por nivel workflow_details userId aprobador, level, statusId, motive, decidedAt

9.2 Reglas de avance (implementadas en ReimbursementService)

  1. Al crear, se lee la plantilla activa de companyId y se materializan N details en PENDING.
  2. Solo el nivel activo (menor level aún PENDING) puede decidir.
  3. El decisor debe ser el userId del detail (o admin).
  4. Aprobar → detail APPROVED + decidedAt; si quedan pendientes, la rendición sigue PENDING.
  5. Si era el último nivel en standard → rendición y workflow pasan a PENDING_PAYMENT; se setean signature / externalId locales si faltaban.
  6. Si era el último nivel en viaticos_apsv → APPROVED y luego PENDING_SETTLEMENT (sin pasar por firma/nómina standard).
  7. Rechazar → motive obligatorio; detail REJECTED; restantes PENDING → REJECTED; rendición DISMISSED.
  8. Cancelar (dueño/admin) solo si PENDING y todos los details aún PENDING → DISMISSED.
  9. Editar mismas precondiciones que cancelar; ownership (propia o admin).
  10. Firmar (/signature): dueño o admin/API Key; todos los niveles APPROVED; pasa a PENDING_PAYMENT.
Flag de UI/API: canEdit = status PENDING y todos los details PENDING. activeLevel expone nivel, userId y detailId del siguiente decisor.

9.3 Diagrama secuencia — aprobación (standard)

Empleado          API/Service              Aprobador L1         Aprobador L2
   │  POST /reimbursements                      │                     │
   │─────────────────────────► create + WF L1,L2 PENDING              │
   │                  PENDING                   │                     │
   │                                            │  POST .../approve   │
   │                                            │◄────────────────────┤
   │                               L1 APPROVED  │                     │
   │                               L2 sigue PENDING                   │
   │                                            │                     │  POST approve
   │                                            │                     │◄──
   │                         PENDING_PAYMENT (último OK)              │
   │  (opcional) POST .../signature                                   │
   │─────────────────────────► PENDING_PAYMENT + externalId           │

10. Adjuntos

11. Importación de personas

Misma lógica para front e integradores (UserService). Persistencia solo vía services; siempre con companyId destino dentro del alcance del token.

CanalEndpoint
Alta unitariaPOST /api/v1/users
Lote JSONPOST /api/v1/users/import
Archivo CSV/XLSXPOST /api/v1/users/import/file (multipart)

Campos mínimos

email, firstName, lastName, documentId, docket (opc.), role(s), companyId, active.

Reglas de import

  1. Validación fila a fila; respuesta con created, updated, skipped, errors[].
  2. Upsert opcional por email+companyId o documentId+companyId.
  3. continueOnError default true (no tumba el lote entero).
  4. Roles y plantillas solo dentro del mismo tenant/rama.
  5. Rate limit y tamaño máx. de archivo.

CSV de prueba: database/seeds/people-import-sample.csv.

12. Servicio de IA externa (n8n OCR)

Adapter configurable: mock, n8n (N8nOcrAdapter) o http genérico. Variables: AI_PROVIDER, AI_API_URL (o N8N_BASE_URL + N8N_WEBHOOK_PATH), N8N_USE_TEST, AI_ENABLED, AI_TIMEOUT_MS, AI_MIN_CONFIDENCE. Contrato: docs/ai-scanner-contract.md. Postman: docs/postman/Rendiciones-OCR.postman_collection.json.

El workflow n8n usa visión (Mistral): POST multipart con campo file → JSON con date, amount, merchant (nullable), currency, confidence, partial, retry_suggested. Si retry_suggested=true, la UI pide nueva foto antes de guardar (no se crea la rendición).

Además del kill switch global (AI_ENABLED), cada empresa se habilita desde el panel web: menú Scanner IA o Empresas → IA: encender (companies.aiScanEnabled). Seed: Sucursal Sur = on.

ModoComportamiento
suggest (default / UI) Analiza → confirma usuario → POST /reimbursements (evita doble llamada a visión)
auto_create Analiza → si confidence OK y no retry → crea + workflow; si retry_suggested → no crea
enrich Sobre PENDING existente: completa campos faltantes (cuando esté expuesto)

Schema n8n

{
  "date": "YYYY-MM-DD",
  "amount": 27.6,
  "merchant": "Casa Pepe",
  "currency": "ARS",
  "confidence": 0.9,
  "partial": false,
  "retry_suggested": false
}

13. Listados, Excel y reportes §11

El listado (GET /reimbursements) incluye usuario, adjuntos y workflow/details (proceso completo). Filtros: fecha desde/hasta, statusId, companyId, paginación; empleados ven solo propias; inbox=1 filtra bandeja del aprobador.

13.1 Export del listado (proceso completo)

Export (GET /reimbursements/export): mismos filtros; genera .xlsx (ExcelExportService) con columnas del proceso:

13.2 Reportes Excel §11 (ReportService)

Distintos del export de listado. Catálogo: GET /reports. Descarga: GET /reports/:code/export (roles admin/approver; scoped por tenant).

CódigoNombreGrupo
solicitudes_todasSolicitudes realizadassolicitudes
solicitudes_aprobadasSolicitudes aprobadassolicitudes
solicitudes_rechazadasSolicitudes rechazadassolicitudes
solicitudes_pendientesSolicitudes pendientessolicitudes
rendiciones_pendientesRendiciones pendientesrendiciones
rendiciones_liquidadasRendiciones liquidadasrendiciones
gastos_periodoGastos por períodogastos
gastos_agenteGastos por agentegastos
gastos_destinoGastos por destinogastos
gastos_areaGastos por áreagastos

13.3 PDF por rendición

GET /reimbursements/:id/pdf — APSV: planilla de liquidación + viáticos/gastos + imágenes adjuntas; standard: comprobante + adjuntos (PdfExportService).

14. Notificaciones (§10)

Canales: bandeja in-app (notifications) + email SMTP opcional. Disparo desde services tras mutaciones (submit, approve/reject, pendiente de rendición, observada, liquidada, etc.).

EndpointAcción
GET /notificationsBandeja del usuario autenticado
POST /notifications/:id/readMarcar una leída
POST /notifications/read-allMarcar todas leídas
VariableUso
NOTIFY_EMAIL_ENABLEDdefault false — activa envío SMTP
SMTP_HOST / SMTP_PORT / SMTP_SECUREServidor
SMTP_USER / SMTP_PASSCredenciales (solo env)
SMTP_FROMRemitente
Si SMTP falla o está off: se registra emailStatus y no se revierte la operación de negocio. Healthcheck de la app es independiente de SMTP.

15. Arquitectura técnica

[ Front propio (public/) ] ──┐
[ ERP / portal / app ] ──────┼──► /api/v1/* ──► Controllers ──► Services ──► Sequelize ──► MSSQL
[ AI Provider externo ] ◄────┘                      │
                                      integrations/ai (adapter n8n/mock/…)
                                      integrations/paysheet (stub/opcional)
                                      NotificationService → SMTP (opcional)
CapaResponsabilidad
routesHTTP, multer, roles
controllersFinos: parse req → service → response
servicesNegocio, transacciones, tenant, workflow, APSV, reportes, notif
modelsSequelize / MSSQL
middlewaresauth JWT/API Key, upload, errors
integrationsIA, PaySheet, SMTP — sin bypass de dominio

Services de dominio (además de Reimbursement / Company / User)

Estructura de carpetas

src/{config,models,migrations,seeders,routes,controllers,services,middlewares,integrations,utils}
public/          → front SPA ligera (sin Vite/Webpack)
landing/         → página comercial
docs/            → wiki, OpenAPI, DBML, pipeline, ENTREGA
database/seeds/  → dataset demo (+ forms/viaticos_apsv.schema.json)
uploads/         → adjuntos runtime
tests/           → unitarios críticos

16. Superficie API /api/v1

Contrato vivo OpenAPI 1.2.0 (~74 operaciones, cobertura completa de src/routes/v1.js): Swagger UI · /api/openapi.json · fuente docs/api/openapi.json.

Guía humana detallada (estados, flujos, tablas, ejemplos): docs/api/API.md. Colección Postman: docs/postman/.

Auth: JWT (Authorization: Bearer) o API Key (X-API-Key). Envelope: { success, message, data, meta? }. Health: GET /health (fuera de v1).

Auth

MétodoRutaNotas
POST/auth/loginemail/password → JWT + user (sin auth previa)

Empresas y form-config

MétodoRutaAcción
GET/companiesListar según scope (?tree=1)
GET/POST/PATCH/DELETE/companies · /:idCRUD / soft delete
GET/companies/:id/treeSubárbol
GET/PUT/companies/:id/form-configLeer / upsert formCode + schema + reglas

Rendiciones (standard + APSV)

MétodoRutaAcción
GET/reimbursementsListar + proceso (+ inbox)
GET/reimbursements/:idDetalle completo
GET/reimbursements/exportExcel/CSV listado
GET/reimbursements/:id/pdfPDF planillas / comprobante
GET/reimbursements/:id/eventsHistorial de eventos
POST/reimbursementsCrear (standard→PENDING; APSV→DRAFT)
POST/reimbursements/:id/updateEditar si canEdit / DRAFT
POST/reimbursements/:id/cancelCancelar → DISMISSED
DELETE/reimbursements/:idCancelar o borrar físico si DISMISSED
POST/reimbursements/:id/approveDecidir nivel activo
POST/reimbursements/:id/signatureFirmar → PENDING_PAYMENT (standard)
POST/reimbursements/:id/payMarcar PAID + comprobantes
POST/reimbursements/:id/submitAPSV: DRAFT → SUBMITTED + workflow
POST/reimbursements/:id/settlementAPSV: etapa 2 → SETTLED
POST/reimbursements/:id/observeAPSV: → OBSERVED
POST/reimbursements/:id/liquidateAPSV: → LIQUIDATED
GET/reimbursements/:id/attachments/:attachmentIdDescarga

Circuito Gastos (missions / person-settlements)

MétodoRutaAcción
GET/POST/expense-typesTipos de gasto
GET/PUT/expense-types/monthly-ratesTarifas del mes
GET/POST/missionsSolicitudes de gasto
POST/missions/:id/submitEnviar a aprobación
GET/person-settlementsLiquidaciones por persona
POST/person-settlements/:id/approveDecidir nivel
PATCH/person-settlements/:id/quantitiesCantidades / importes
POST/person-settlements/:id/liquidateLiquidar

Notificaciones, reportes, users, AI, workflows

17. Modelo de datos (DER)

Fuente DBML: docs/schema.dbml (visualizar en dbdiagram.io).

Entidades centrales:

Naming en inglés en tablas/columnas. No se portan namespaces PHP del legacy; sí el comportamiento de estados, ownership y callbacks.

18. Autenticación e integración

MecanismoCómo
JWT usuario Authorization: Bearer <token> — claims: sub, email, role, companyId, scopeCompanyIds
API Key Header X-API-Key — resuelve companyId + scope (hijos si inherit)

Para embeber en otro sistema: apuntar el front con window.API_BASE_URL, configurar CORS_ORIGINS, usar la misma OpenAPI. Seed demo incluye demo-api-key-holding-001 (solo no-prod).

19. Reglas invariantes (checklist de negocio)

  1. Empleado crea/edita/cancela/firma solo rendiciones propias.
  2. Aprobadores actúan solo en su nivel asignado y cuando es el nivel activo.
  3. No editar ni cancelar si algún nivel ya fue revisado (standard PENDING).
  4. Firma solo con niveles completos APPROVED (standard; sin re-firmar si ya informada).
  5. Callback nómina no puede devolver a PENDING.
  6. Toda query operativa filtra por companyId / scopeCompanyIds.
  7. Create / update / cancel / approve / signature / submit / settlement en transacciones.
  8. IA nunca escribe directo en MSSQL ni saltea workflow.
  9. Import personas no sale del árbol permitido del token/API Key.
  10. parentId no puede formar ciclos.
  11. Create standard exige plantilla de workflow activa; APSV exige plantilla al submit.
  12. Settlement / observe / liquidate solo con formCode=viaticos_apsv y estados válidos.
  13. Fallo de email no revierte la mutación de negocio.

20. Front propio

App estática en public/ (HTML/CSS/JS). No usa Vite ni Webpack (estándar del proyecto). Consume únicamente HTTP/JSON (+ multipart). Pantallas alineadas a los flujos de §8: listado con badges de estado, alta standard, wizard APSV (2 etapas), bandeja de aprobación, admin de empresas/form-config, import de personas, plantillas, asistente IA (si aiScanEnabled), campana de notificaciones, reportes §11, descarga PDF.

Landing comercial separada: landing/index.html (ruta típica /landing/).

21. Set de datos de prueba

Nunca usar en producción. Password demo documentada: Demo123!

UsuarioRolEmpresa
admin@holding.demoadmin raízHolding (id=1) · standard
admin@sur.demoadminSucursal Sur (id=2) · standard
empleado@sur.demoemployeeSur
aprobador1@sur.demoapprover L1Sur
aprobador2@sur.demoapprover L2Sur
empleado@norte.demo / admin@norte.demoNorte (id=3) · standard
solicitante@apsv.demoemployeeAPSV (id=4) · viaticos_apsv
jefe@apsv.demoapprover L1APSV
gerente@apsv.demoapprover L2APSV
admin@apsv.demoadmin / L3APSV

Escenarios standard: PENDING sin revisar · PENDING con L1 OK · DISMISSED · aislamiento Norte/Sur · CSV import. APSV: casos en DRAFT, SUBMITTED, PENDING_SETTLEMENT, SETTLED, OBSERVED, LIQUIDATED; plantilla 3 niveles; schema en forms/viaticos_apsv.schema.json. Sur tiene aiScanEnabled=true; APSV off.

npx sequelize-cli db:migrate
npm run seed:demo
# fixtures adjuntos: npm run db:fixtures && npm run db:attachments

Detalle: database/seeds/README.md.

22. Seguridad (mínimo)

23. Operación y despliegue

cp .env.example .env   # DB_* → SQL Server externo; SMTP_* / AI_* según entorno
npm install
npx sequelize-cli db:migrate
npm run seed:demo
npm run dev            # o: docker compose up --build

24. Troubleshooting

SíntomaCausa probableQué hacer
Login 401 Sin seed / password distinta / DB Verificar migrate + seed; password Demo123!
Create 400 “No hay plantilla…” Empresa sin workflow template activo Crear/activar plantilla en esa companyId
Create 400 sin adjunto Falta files[] (standard) Enviar multipart con al menos un archivo; APSV DRAFT puede relajar campos
Approve 403 No es el aprobador del nivel activo Usar el userId del activeLevel o admin
Edit/cancel 400 Ya hubo una revisión de nivel Esperado: canEdit=false
submit / settlement 400 formCode ≠ viaticos_apsv o estado inválido Verificar form-config empresa APSV y estado actual
IA 503 / 403 AI off o aiScanEnabled=false AI_PROVIDER=mock o habilitar Sur / PATCH company
Email no llega NOTIFY_EMAIL_ENABLED=false o SMTP mal Revisar env; la operación de negocio igual debe quedar OK
Datos de otra sucursal Scope incorrecto Revisar companyId del token y parentId