Skip to content

Flujo Completo de Facturación Electrónica

Módulo: Ventas Tipo: Process Estado: Implementado Fecha: 2026-03-05


Descripción

Diagrama de flujo del proceso de facturación electrónica tal como está implementado hoy en ComprobanteService, FacturaService y NotaCreditoService. Muestra todas las ramas reales: modo prueba, modo oficial, condición de venta, registro en ARCA, efectos laterales (stock, tesorería, cuenta corriente) y manejo de errores.


Flujo Completo

mermaid
flowchart TD
    %% -----------------------------------------------
    %% ENTRADA
    %% -----------------------------------------------
    START([Request HTTP<br/>comprobanteController.insert])
    START --> MAKE_REQUEST["ComprobanteRequest::make(data)<br/>Validate (true)"]
    MAKE_REQUEST --> MAKE_SERVICE["ComprobanteServiceFactory::make(codigo)<br/>Instancia FacturaService /<br/>NotaCreditoService / NotaDebitoService"]

    MAKE_SERVICE --> SET_PRUEBA{request.prueba?}
    SET_PRUEBA -->|SI| PRUEBA_ON["setPrueba()<br/>prueba = true"]
    SET_PRUEBA -->|NO| FETCH_DATA
    PRUEBA_ON --> FETCH_DATA

    %% -----------------------------------------------
    %% DATOS DE CONTEXTO
    %% -----------------------------------------------
    FETCH_DATA["Consulta contexto:<br/>• empresa (Empres)<br/>• cliente (Cliente)<br/>• tipoComprobante (TipoComprobante::getById)<br/>• turno (Turno::getTurno)"]

    FETCH_DATA --> HAS_ASOC{"¿Tiene comprobante<br/>asociado?"}
    HAS_ASOC -->|SI| ASOC_VALID["Valida que el comprobante<br/>asociado exista en BD"]
    ASOC_VALID --> ASOC_ERR{"¿Existe?"}
    ASOC_ERR -->|NO| ERR_ASOC["BadRequest:<br/>No se encontró el<br/>comprobante asociado"]
    ASOC_ERR -->|SI| CALC_DOMAIN
    HAS_ASOC -->|NO| CALC_DOMAIN

    %% -----------------------------------------------
    %% CÁLCULO DE DOMINIO
    %% -----------------------------------------------
    CALC_DOMAIN["ComprobanteDomain::calculate()<br/>• Procesa ítems (precio × cantidad)<br/>• Aplica bonificaciones por ítem<br/>• Calcula IVA / Impuesto Interno<br/>• Aplica recargo condición de venta<br/>• Totaliza: netoGravado, netoNoGravado,<br/>  excento, ivaTotal, impInterno, total"]

    CALC_DOMAIN --> MAP_COMP["mapComprobante()<br/>Construye ComprobanteDTO<br/>con datos calculados"]

    %% -----------------------------------------------
    %% VALIDACIÓN CTA CTE
    %% -----------------------------------------------
    MAP_COMP --> COND_VENTA{condicionVenta<br/>= CTA_CTE?}
    COND_VENTA -->|SI| VAL_CTACTE["validateCtaCte()<br/>• Cliente tiene cuenta corriente habilitada<br/>• Total + saldo actual < margen de crédito<br/>• Facturas impagas < límite configurado"]
    VAL_CTACTE --> CTACTE_OK{"¿Valida?"}
    CTACTE_OK -->|NO| ERR_CTACTE["BadRequest:<br/>• Sin habilitación CtaCte, o<br/>• Límite de crédito excedido, o<br/>• Max. comprobantes impagos"]
    CTACTE_OK -->|SI| ARCA_BRANCH
    COND_VENTA -->|NO| ARCA_BRANCH

    %% -----------------------------------------------
    %% BRANCH ARCA / PRUEBA
    %% -----------------------------------------------
    ARCA_BRANCH{!prueba AND<br/>registraARCA?}

    %% MODO OFICIAL → ARCA real
    ARCA_BRANCH -->|"SI — modo oficial"| ARCA_LAST["ArcaClient::forCompany(cuit)<br/>wsfe::getLastVoucher(sucursal, codigo)<br/>nrocomp = último + 1"]
    ARCA_LAST --> ARCA_FORMAT["formatToArcaRequest(comp)<br/>• PtoVta, CbteTipo, CbteDesde/Hasta<br/>• DocTipo, DocNro<br/>• Importes: ImpTotal, ImpNeto, ImpIVA, ImpTrib<br/>• Alícuotas IVA por ítem<br/>• Tributos internos por ítem<br/>• CondicionIVAReceptorId"]
    ARCA_FORMAT --> ARCA_VALIDATE["arcaRequest.validate(true)"]
    ARCA_VALIDATE --> ARCA_CREATE["wsfe::createVoucher(request)"]
    ARCA_CREATE --> ARCA_RESP{"¿Respuesta<br/>de ARCA?"}
    ARCA_RESP -->|"Error de conexión o timeout"| ERR_ARCA_CONN["ArcaException:<br/>Error inesperado al<br/>procesar con ARCA"]
    ARCA_RESP -->|"Rechazado por ARCA"| ERR_ARCA_REJ["Re-lanza BadRequest<br/>o ArcaException<br/>con detalle del rechazo"]
    ARCA_RESP -->|"Aprobado"| ARCA_OK["comp.cae = arcaRes.CAE<br/>comp.caeVto = arcaRes.CAEFchVto<br/>comp.nrocomp = nrocomp calculado"]
    ARCA_OK --> BEGIN_TX

    %% MODO PRUEBA → numerador local
    ARCA_BRANCH -->|"NO — prueba o sin ARCA"| IS_MANUAL{"¿Es RegistroManualRequest?"}
    IS_MANUAL -->|"SI — manual"| MANUAL["comp.cae = request.CAE<br/>comp.caeVto = request.fechaVtoCAE<br/>comp.nrocomp = request.numeroComprobante<br/>comp.manual = true"]
    IS_MANUAL -->|"NO — prueba"| PRUEBA_NRO["getNewNumeradorComprobante(codigo)<br/>Número siguiente desde BD local"]
    MANUAL --> BEGIN_TX
    PRUEBA_NRO --> BEGIN_TX

    %% -----------------------------------------------
    %% TRANSACCIÓN PRINCIPAL
    %% -----------------------------------------------
    BEGIN_TX["beginTransaction('oficial', 'principal')"]

    BEGIN_TX --> INSERT_COMP["insertComprobante(comp, request)<br/>model.insert(comp)<br/>→ Guarda en tabla factura /<br/>nota_credito / nota_debito"]

    INSERT_COMP --> HAS_PEND{"¿Tiene pendientes?<br/>solo en Factura"}
    HAS_PEND -->|SI| PROC_PEND["processPendientes(pendientes, idFactura)<br/>Por cada pendiente:<br/>• PendienteService::patch → vincula id_factura<br/>• Si Stock activo: deleteByIdPendiente<br/>  (elimina movimiento stock previo)"]
    HAS_PEND -->|NO| AUDIT
    PROC_PEND --> AUDIT

    AUDIT["registrarAuditoria('INSERT', tipoComprobante, tabla, id)"]

    AUDIT --> HAS_ITEMS{"¿Tiene items?"}

    %% -----------------------------------------------
    %% INSERT ITEMS + STOCK
    %% -----------------------------------------------
    HAS_ITEMS -->|SI| INSERT_ITEMS["ItemFacturaService::insert(items)<br/>Guarda en tabla items_subdicom"]
    HAS_ITEMS -->|NO| CTACTE_REG

    INSERT_ITEMS --> STOCK_MOD{"¿Modulo Stock<br/>activo?"}
    STOCK_MOD -->|NO| CTACTE_REG
    STOCK_MOD -->|SI| BRIDGE["BridgeGlobalVentasService<br/>::generateOrGetGlobalId(id, codigo, schema)<br/>Genera UUID global para trazabilidad"]

    BRIDGE --> STOCK_LOOP["Por cada ítem con maneja_stock = S"]

    STOCK_LOOP --> TIPO_COMP_STOCK{Tipo de<br/>comprobante}
    TIPO_COMP_STOCK -->|"Factura"| STOCK_EGR["MovimientoStock EGRESO<br/>(descuenta stock)<br/>marca = OFICIAL o PRUEBA<br/>según conexión BD"]
    TIPO_COMP_STOCK -->|"Nota de Crédito"| STOCK_ING["MovimientoStock INGRESO<br/>(restituye stock)<br/>marca = OFICIAL o PRUEBA"]
    TIPO_COMP_STOCK -->|"Nota de Débito"| CTACTE_REG
    STOCK_EGR --> CTACTE_REG
    STOCK_ING --> CTACTE_REG

    %% -----------------------------------------------
    %% CUENTA CORRIENTE
    %% -----------------------------------------------
    CTACTE_REG{condicionVenta<br/>= CTA_CTE?}
    CTACTE_REG -->|NO| MOV_CAJA_CHECK
    CTACTE_REG -->|SI| INSERT_CTACTE

    subgraph INSERT_CTACTE["Registro en Cuenta Corriente"]
        direction TB
        CC1{Tipo de<br/>comprobante}
        CC2["insertMovimientoCtaCte DEBE<br/>ordcta.debe = total"]
        CC3["insertMovimientoCtaCte HABER<br/>ordcta.haber = total"]
        CC4{"¿NC con comprobante<br/>asociado?"}
        CC5["getMovimiento(nrocomp, tipo)<br/>Actualiza saldo y fecha pago<br/>de la factura original"]
        CC1 -->|"Factura / ND"| CC2
        CC1 -->|"Nota de Crédito"| CC3 --> CC4
        CC4 -->|"SI"| CC5
        CC4 -->|"NO"| CC_END((ok))
        CC2 --> CC_END
        CC5 --> CC_END
    end

    INSERT_CTACTE --> MOV_CAJA_CHECK

    %% -----------------------------------------------
    %% MOVIMIENTO DE CAJA (TESORERÍA)
    %% -----------------------------------------------
    MOV_CAJA_CHECK{registraMovimientoCaja<br/>= true?}
    MOV_CAJA_CHECK -->|NO| COMMIT
    MOV_CAJA_CHECK -->|SI| COND_CAJA{condicionVenta<br/>= CTA_CTE?}
    COND_CAJA -->|SI — no registra caja| COMMIT
    COND_CAJA -->|NO| TES_MOD{"¿Modulo Tesoreria<br/>activo?"}
    TES_MOD -->|NO| COMMIT
    TES_MOD -->|SI| CAJA_CUENTA["CuentaAsignadaService<br/>::getOne(VENTAS_CONTADO)<br/>Obtiene cuenta contable"]

    CAJA_CUENTA --> INSERT_MOV_CAJA

    subgraph INSERT_MOV_CAJA["Registro Movimiento de Caja"]
        direction TB
        MC1{Tipo de<br/>comprobante}
        MC2["MovimientoCaja ENTRADA<br/>cobro contado de factura"]
        MC3["MovimientoCaja SALIDA<br/>devolución NC o ND"]
        MC4{condicionVenta<br/>= TARJETA?}
        MC5["2do MovimientoCaja tipo opuesto<br/>movimiento bancario tarjeta<br/>cuenta = tarjeta.cuenta"]
        MC1 -->|"Factura"| MC2
        MC1 -->|"NC / ND"| MC3
        MC2 --> MC4
        MC3 --> MC4
        MC4 -->|"SI"| MC5
        MC4 -->|"NO"| MC_END((ok))
        MC5 --> MC_END
    end

    INSERT_MOV_CAJA --> COMMIT

    %% -----------------------------------------------
    %% COMMIT / ROLLBACK
    %% -----------------------------------------------
    COMMIT["connections.commit()"]
    COMMIT --> SUCCESS

    SUCCESS["Retorna:<br/>{ nrocomp, codigo, reporte }"]
    SUCCESS --> END_OK([Comprobante registrado])

    %% -----------------------------------------------
    %% MANEJO DE ERRORES EN TRANSACCIÓN
    %% -----------------------------------------------
    BEGIN_TX --> ON_EX["Exception en<br/>transacción"]
    ON_EX --> ROLLBACK["connections.rollback()"]
    ROLLBACK --> WAS_ARCA{"¿Modo oficial?<br/>ARCA ya registro"}
    WAS_ARCA -->|"SI — ARCA aprobó<br/>pero BD falló"| ERR_ARCA_PARTIAL["ErrorHandler::logToDatabase(e)<br/>Retorna error descriptivo<br/>+ datos del comp (para recuperación manual)"]
    WAS_ARCA -->|"NO — prueba<br/>o sin ARCA"| RETHROW["Re-lanza excepción"]

    %% -----------------------------------------------
    %% ESTILOS
    %% -----------------------------------------------
    style START fill:#e8f4fd,stroke:#2196f3
    style END_OK fill:#e8f5e9,stroke:#4caf50
    style SUCCESS fill:#e8f5e9,stroke:#4caf50

    style ERR_ASOC fill:#fdecea,stroke:#f44336,color:#b71c1c
    style ERR_CTACTE fill:#fdecea,stroke:#f44336,color:#b71c1c
    style ERR_ARCA_CONN fill:#fdecea,stroke:#f44336,color:#b71c1c
    style ERR_ARCA_REJ fill:#fdecea,stroke:#f44336,color:#b71c1c
    style ERR_ARCA_PARTIAL fill:#fff3e0,stroke:#ff9800,color:#e65100
    style RETHROW fill:#fdecea,stroke:#f44336,color:#b71c1c

    style ARCA_OK fill:#e8f5e9,stroke:#4caf50
    style ARCA_CREATE fill:#fff8e1,stroke:#ffc107
    style ARCA_FORMAT fill:#fff8e1,stroke:#ffc107
    style ARCA_LAST fill:#fff8e1,stroke:#ffc107

    style PRUEBA_ON fill:#f3e5f5,stroke:#9c27b0
    style PRUEBA_NRO fill:#f3e5f5,stroke:#9c27b0

    style BEGIN_TX fill:#e3f2fd,stroke:#1565c0
    style COMMIT fill:#e3f2fd,stroke:#1565c0
    style ROLLBACK fill:#fdecea,stroke:#f44336

Resumen de efectos laterales por tipo de comprobante

EfectoFacturaNota de CréditoNota de Débito
Inserta comprobante
Inserta ítems✅ (si tiene)✅ (si tiene)✅ (si tiene)
Stock (si módulo activo)EGRESO por ítemINGRESO por ítem
CtaCte (si condición = CTA_CTE)DEBEHABER + cancela facturaDEBE
Tesorería (si módulo activo y no CTA_CTE)ENTRADASALIDASALIDA
2do movimiento caja (TARJETA)✅ (débito bancario)
Procesa pendientes✅ (si tiene)
Registro en ARCA✅ (modo oficial)✅ (modo oficial)✅ (modo oficial)
Auditoría

Comportamiento del error post-ARCA

Cuando ARCA aprueba el comprobante pero la transacción de BD falla, el sistema no puede revertir el registro en ARCA (es externo). El flujo real:

  1. Hace rollback de la BD
  2. Llama a ErrorHandler::logToDatabase($e) para dejar trazabilidad
  3. Retorna un objeto de error con los datos del comprobante (para recuperación manual)
  4. El comprobante queda aprobado en ARCA pero sin registro en BD → requiere intervención manual con registro manual (RegistroManualRequest)

Archivos de código relevantes

ClaseRutaRol
ComprobanteServiceservice/Venta/Facturacion/ComprobanteService.phpBase del flujo de inserción
FacturaServiceservice/Venta/Facturacion/FacturaService.phpStock EGRESO, pendientes, mov caja ENTRADA
NotaCreditoServiceservice/Venta/Facturacion/NotaCreditoService.phpStock INGRESO, mov caja SALIDA, cancela CtaCte
ComprobanteControllercontroller/modulo-venta/Facturacion/ComprobanteController.phpEntry point HTTP
ArcaClientApi/Arca/ArcaClient.phpCliente WSFE de ARCA/AFIP
MovimientoStockServiceservice/Stock/MovimientoStockService.phpRegistro de stock
MovimientoCajaServiceFactoryFactories/Tesoreria/MovimientoCajaServiceFactory.phpRegistro en caja
CtaCteControllerFactoryFactories/CtaCte/CtaCteControllerFactory.phpRegistro en cuenta corriente

Documentos relacionados