Plataforma técnica · Orchestrator
Existencias Service
Orchestrator Inventarios Existencias
ExistenciasService administra el catálogo de productos que componen el inventario (administracion.existencias o similar — schema interno). Cada existencia es una entidad maestra que después se mueve vía StockService y se ingresa vía ComprasInventarioService.
No emite hooks de dominio — es CRUD puro con validación cruzada de categoría.
Operaciones
Section titled “Operaciones”| Método | Para |
|---|---|
getExistencias(ctx, filtros?) | Lista con filtros opcionales (giro, tipo, categoría, giros empresa). |
getExistenciaById(ctx, id) | Lookup. Lanza NotFoundError("Existencia", id). |
createExistencia(ctx, data) | Crea con auto-generación de código si no se especifica. |
updateExistencia(ctx, id, data) | Edita campos del catálogo. |
eliminarExistencia(ctx, id) | Borra. Si hay movimientos asociados, lanza ValidationError (FK 23503). |
getResumenExistencias(ctx, filtros) | Vista agregada (saldos, valorización) para reportes. |
Modelo
Section titled “Modelo”Existencia (campos clave):
| Campo | Tipo | Notas |
|---|---|---|
id | UUID | |
codigo | string | Único; auto-generado si no se especifica. |
nombre | string | Requerido en create. |
giro | string | Requerido. Filtra qué existencias ve qué empresa. |
tipo_existencia | string | Requerido. Influye en el código autogenerado. |
unidad_medida | string | Requerido (e.g. UND, KG). |
categoria_id | number? | FK a categorias_existencia. |
categoria (texto) | string? | Snapshot del nombre de categoría al crear/actualizar. |
cuenta_existencia_codigo | string | Heredada de la categoría (1109xxx). |
activa | boolean | Filtro default en listados. |
created_at / updated_at | — |
Flujo de createExistencia
Section titled “Flujo de createExistencia”flowchart TB
IN["createExistencia(data)"]
V["validateRequired<br/>(nombre, giro, tipo_existencia, unidad_medida)"]
TX["withTransaction"]
CAT{"data.categoria_id?"}
CHK["CategoriasExistenciaRepository<br/>.findCategoriaById"]
ERR["NotFoundError<br/>'CategoriaExistencia'"]
GEN{"data.codigo trimmed<br/>length > 0?"}
USE["codigo = data.codigo.toUpperCase()"]
AUTO["ExistenciasRepository<br/>.generateExistenciaCodigo<br/>(categoriaNombre, tipoExistencia)"]
INS["createExistencia(client, { ...data, codigo }, userId)"]
RET["ServiceResult(existencia)"]
IN --> V --> TX --> CAT
CAT -- sí --> CHK
CHK -- no encontrado --> ERR
CHK -- ok --> GEN
CAT -- no --> GEN
GEN -- sí --> USE --> INS
GEN -- no --> AUTO --> INS
INS --> RET Snapshot del nombre de categoría
Section titled “Snapshot del nombre de categoría”Cuando se pasa categoria_id, el service también guarda categoria (string) con el nombre actual de la categoría. Esto es un snapshot — si la categoría se renombra después, las existencias mantienen su nombre histórico. Si necesitas el nombre vigente, hacer JOIN con categorias_existencia al consultar.
Autogeneración de código
Section titled “Autogeneración de código”Si el caller no pasa codigo (o pasa string vacío), ExistenciasRepository.generateExistenciaCodigo({ categoriaNombre, tipoExistencia }) lo genera. La lógica vive en el repository — típicamente concatena un prefijo del tipo + slug de categoría + correlativo, todo en mayúsculas.
Si el caller pasa un codigo, se respeta tal cual (uppercase normalizado).
Validación cruzada en update
Section titled “Validación cruzada en update”updateExistencia también valida que el categoria_id (si se cambia) exista. No revalida el resto de campos requeridos — confía en que ya existían correctos en la creación.
eliminarExistencia — manejo de FK
Section titled “eliminarExistencia — manejo de FK”async eliminarExistencia(ctx, id) {const pool = this.getPool(ctx.tenantDb);try { const deleted = await ExistenciasRepository.deleteExistencia(pool, id); return this.success({ deleted });} catch (e) { if (isPgError(e) && e.code === '23503') { throw new ValidationError( 'No se puede eliminar la existencia porque tiene movimientos o referencias asociadas.', ); } throw e;}}Postgres 23503 = foreign_key_violation. La existencia se usa como FK desde:
movimientos_inventario.existencia_id- Posiblemente otras tablas downstream
El servicio traduce el error crudo a un mensaje accionable. No verifica antes del DELETE — confía en que el FK rechazará. Esto es preferible: el check + delete tendrían condición de carrera; el FK es atómico.
getResumenExistencias
Section titled “getResumenExistencias”Devuelve una vista agregada para reportes (saldo, valorización, último movimiento, etc.). El cálculo vive en ExistenciasRepository.findResumenExistencias. Acepta filtros similares al listado normal pero retorna fields agregados.
Esta vista es read-only — StockService.getStockActual complementa con detalle de movimientos vigentes.
Por qué no hay hook
Section titled “Por qué no hay hook”El catálogo es estructural: cambiar un nombre o categoría no dispara cadenas reactivas. Los hooks del dominio inventario se emiten en los movimientos (compra_inv:contabilizada, existencia:castigada, costo:egreso_calculado), no en el CRUD del maestro.
Si en el futuro se quiere notificar cambios de catálogo (e.g. para invalidar cache de UI), agregar existencia:created/updated/deleted al DomainEventMap.