State Machines: Entity Lifecycle and Transition Rules
Every entity in B2B Import ERP follows a strict state machine. Transitions are validated by the domain layer, audited, and guarded by RBAC permissions.
Use this file to discover all available pages before exploring further.
Every major entity in B2B Import ERP — clients, requirements, quotes, jobs, invoices, and more — has a formally defined lifecycle expressed as a state machine. Instead of allowing free-form status updates, the platform only permits transitions that are explicitly declared in the Master State & Transition Matrix. This design eliminates invalid data states, makes the system auditable, and provides a single authoritative source of truth for business process enforcement.
All state names are in Spanish because B2B Import ERP is built for Spanish-speaking markets in Latin America. Database column values use uppercase snake_case codes (e.g., "BORRADOR", "EN_REVISION", "APROBADA", "RECHAZADA", "VENCIDA" for quotes), while this document uses human-readable Spanish labels for clarity.
No state change may ever be applied directly in the database. All transitions must be executed through domain services.
Every state transition — regardless of entity type — must:
Validate permissions — the acting user must hold the required RBAC permission for the transition (e.g., quotes.approve to move a quote from Enviada → Aprobada).
Record an audit log entry — the audit_log table captures old_values, new_values, user_id, tenant_id, ip_address, and user_agent.
Emit a domain event — downstream listeners (notifications, recalculations, integrations) react to the event asynchronously.
Generate alerts if applicable — certain transitions trigger ALERTA records (e.g., a quote reaching Vencida, a job moving to Suspendido).
Update related dates — timestamps such as started_at, completed_at, and closed_at are set automatically by the transition service, not by the caller.
The domain service that executes a transition always performs the following sequence atomically:
// Pseudo-code — domain transition service patternasync function transitionState(entityId, targetState, actingUserId) { // 1. Load current entity and validate the transition is allowed const entity = await repo.findById(entityId); assertTransitionAllowed(entity.state, targetState); // 2. Check RBAC permission for this specific transition await assertPermission(actingUserId, requiredPermission(targetState)); // 3. Validate domain preconditions (responsible user, linked records, etc.) await validatePreconditions(entity, targetState); // 4. Apply the state change and update related timestamps await repo.updateState(entityId, targetState); // 5. Write audit log entry await auditLog.record({ entityId, from: entity.state, to: targetState, actingUserId }); // 6. Emit domain event for downstream consumers await eventBus.emit(`${entity.type}.state_changed`, { entityId, targetState }); // 7. Create alert records if the transition triggers one await alertService.maybeCreate(entity, targetState);}
If a transition is not defined in the Master State & Transition Matrix, it must not be implemented, inferred, or invented. Stop and request a formal definition before writing any code.