Skip to main content

Módulo de Facturación

El módulo de facturación es el núcleo del sistema, permitiendo la emisión de facturas (01) y boletas (03) electrónicas con generación automática de XML, firma digital y envío directo a SUNAT.

Características Principales

Emisión de Comprobantes

Facturas, boletas y notas de venta con validación SUNAT

Gestión de Stock

Descuento automático de inventario al emitir ventas

Múltiples Monedas

Soporte para PEN y USD con tipo de cambio

Productos Libres

Añade productos no registrados durante la venta

Tipos de Documentos

El sistema maneja tres tipos principales de documentos de venta:
  • Requisitos: RUC de 11 dígitos obligatorio
  • IGV: Incluido en el total (tasa 18%)
  • Envío SUNAT: Sincrónico vía SOAP
  • Serie: F001, F002, etc.
  • Requisitos: DNI u otro documento (no RUC)
  • IGV: Incluido en el total
  • Envío SUNAT: Requiere Resumen Diario
  • Serie: B001, B002, etc.
  • Uso interno: No se envía a SUNAT
  • Sin stock: No afecta inventario por defecto
  • Convertible: Puede convertirse a factura/boleta
  • Serie: NV01, NV02, etc.

Flujo de Trabajo

1

Seleccionar Cliente

Busca un cliente existente por documento o crea uno nuevo al vuelo. El sistema valida:
  • Facturas requieren RUC (11 dígitos)
  • Boletas no permiten RUC (usar DNI o CE)
  • Cliente “CLIENTES VARIOS” (00000000) para ventas sin datos
2

Agregar Productos

Añade productos del catálogo o productos libres con descripción personalizada:
  • Productos del catálogo: Descuentan stock automáticamente
  • Productos libres: Se crean con código auto-generado (LIB-0001)
  • Snapshot: Se guarda nombre y código actual del producto
3

Configurar Pagos

Define el método de pago y tipo:
  • Contado: Pago inmediato, un solo método
  • Crédito: Configura cuotas con fechas de vencimiento
  • Voucher: Opcional, sube imagen del comprobante de pago
4

Generar Comprobante

El sistema automáticamente:
  • Asigna el siguiente número correlativo para la serie
  • Descuenta stock del almacén configurado
  • Registra movimientos de stock con trazabilidad completa
  • Genera el comprobante en estado “Pendiente SUNAT”
5

Enviar a SUNAT

Usa el botón de envío para:
  • Generar XML firmado digitalmente
  • Enviar vía SOAP (facturas) o preparar para Resumen Diario (boletas)
  • Recibir CDR (Constancia de Recepción) de SUNAT
  • Actualizar estado a “Aceptado” o “Rechazado”

Implementación Técnica

Backend: VentasController

El controlador principal gestiona todas las operaciones de venta:
// app/Http/Controllers/VentasController.php

public function store(Request $request): JsonResponse
{
    return DB::transaction(function () use ($validated, $user) {
        // 1. Crear o buscar cliente
        $idCliente = $validated['id_cliente'] ?? null;
        if (!$idCliente) {
            $clienteModel = Cliente::where('documento', $docCliente)
                ->where('id_empresa', $user->id_empresa)
                ->first();
            
            if (!$clienteModel) {
                $clienteModel = Cliente::create([...]);
            }
        }

        // 2. Obtener próximo número correlativo
        $ultimaVenta = Venta::where('serie', $validated['serie'])
            ->max('numero') ?? 0;
        $numeroBase = DB::table('documentos_empresas')
            ->where('serie', $validated['serie'])
            ->value('numero') ?? 0;
        $proximoNumero = max($ultimaVenta, $numeroBase) + 1;

        // 3. Crear venta
        $venta = Venta::create([...]);

        // 4. Crear productos y descontar stock
        foreach ($validated['productos'] as $producto) {
            ProductoVenta::create([...]);
            
            if ($afectaStock && !$esProductoLibre) {
                $productoModel->decrement('cantidad', $cantidad);
                MovimientoStock::create([...]);
            }
        }

        return response()->json(['success' => true, 'venta' => $venta]);
    });
}

Modelo: Venta

El modelo incluye relaciones y scopes útiles:
// app/Models/Venta.php (líneas 79-129)

public function cliente(): BelongsTo
{
    return $this->belongsTo(Cliente::class, 'id_cliente');
}

public function productosVentas(): HasMany
{
    return $this->hasMany(ProductoVenta::class, 'id_venta');
}

public function pagos(): HasMany
{
    return $this->hasMany(VentaPago::class, 'id_venta');
}

public function scopeActivas($query)
{
    return $query->where('estado', '!=', '2');
}

Validaciones Clave

El sistema implementa validaciones estrictas según normativa SUNAT:
// Factura requiere RUC de 11 dígitos
if ($validated['id_tido'] == 2 && strlen($documento) !== 11) {
    return response()->json([
        'success' => false,
        'message' => 'Para FACTURA se requiere RUC (11 dígitos)'
    ], 422);
}

// Boleta no permite RUC
if ($validated['id_tido'] == 1 && strlen($documento) === 11) {
    return response()->json([
        'success' => false,
        'message' => 'Para BOLETA use DNI u CE. Para RUC emita Factura'
    ], 422);
}

Gestión de Stock

El sistema maneja dos tipos de almacenes:

Almacén Virtual (1)

Stock principal que se descuenta al crear la venta

Almacén Real (2)

Stock físico que se descuenta manualmente después del despacho

Descuento Dual de Stock

Para mayor control, el sistema permite descuentos en dos momentos:
// VentasController.php:537-599
public function descontarStock(Request $request, int $id): JsonResponse
{
    return DB::transaction(function () use ($id, $user) {
        $venta = Venta::where('stock_real_descontado', false)
            ->findOrFail($id);

        foreach ($venta->productosVentas as $detalle) {
            // Buscar producto en almacén 2 por código
            $productoAlmacen2 = Producto::where('almacen', '2')
                ->where('codigo', $detalle->producto->codigo)
                ->first();

            if ($productoAlmacen2) {
                $productoAlmacen2->decrement('cantidad', $detalle->cantidad);
                MovimientoStock::create([...]);
            }
        }

        $venta->update(['stock_real_descontado' => true]);
    });
}

Anulación de Ventas

La anulación devuelve el stock al almacén original:
1

Verificar Estado

Solo se pueden anular ventas activas (estado = 1)
2

Cambiar Estado

La venta pasa a estado “Anulada” (estado = 2)
3

Retornar Stock

Si la venta afectó stock, se devuelve cada producto:
$producto->increment('cantidad', $detalle->cantidad);
MovimientoStock::create([
    'tipo_movimiento' => 'entrada',
    'tipo_documento' => 'anulacion_venta',
    'motivo' => 'Anulación de venta'
]);
4

Registrar Anulación

Se guarda en la tabla ventas_anuladas con motivo y usuario
5

Comunicación de Baja

Si la venta fue enviada a SUNAT, se debe emitir Comunicación de Baja

Productos Libres

Permite vender productos no registrados previamente:
Cuando se ingresa un producto libre, el sistema:
  1. Busca si existe un producto con ese nombre exacto
  2. Si no existe, lo crea con código auto-generado LIB-XXXX
  3. Lo guarda en el catálogo con precio cero de costo
  4. NO descuenta stock (cantidad = 0)
// VentasController.php:231-261
if (!$idProducto && $esProductoLibre) {
    $nombreLibre = trim($producto['descripcion_libre']);
    $productoLibre = Producto::where('nombre', $nombreLibre)
        ->where('id_empresa', $user->id_empresa)
        ->first();

    if (!$productoLibre) {
        // Generar código automático LIB-0001, LIB-0002, etc.
        $ultimoCodigo = Producto::where('codigo', 'LIKE', 'LIB-%')
            ->count();
        $codigoAuto = 'LIB-' . str_pad($ultimoCodigo + 1, 4, '0');

        $productoLibre = Producto::create([
            'codigo' => $codigoAuto,
            'nombre' => $nombreLibre,
            'precio' => $producto['precio_unitario'],
            'cantidad' => 0  // No tiene stock
        ]);
    }
}

Series y Numeración

El sistema gestiona series correlativas automáticamente:
Las series se configuran en la tabla documentos_empresas:
  • Facturas: F001, F002, F003…
  • Boletas: B001, B002, B003…
  • Notas de Venta: NV01, NV02…
Cada serie mantiene su propio correlativo independiente.
El número se genera usando el máximo entre:
  1. Último número usado en la tabla ventas
  2. Número base configurado en documentos_empresas
Esto permite iniciar desde un número específico (ej: empezar en 1000).
// VentasController.php:182-198
$ultimaVenta = Venta::where('serie', $validated['serie'])
    ->max('numero') ?? 0;

$numeroBase = DB::table('documentos_empresas')
    ->where('serie', $validated['serie'])
    ->value('numero') ?? 0;

$proximoNumero = max($ultimaVenta, $numeroBase) + 1;

// Sincronizar la configuración
DB::table('documentos_empresas')
    ->where('serie', $validated['serie'])
    ->update(['numero' => $proximoNumero]);

Conversión desde Cotizaciones

Las cotizaciones aprobadas pueden convertirse en ventas:
// VentasController.php:336-341
if (!empty($validated['cotizacion_id'])) {
    Cotizacion::where('id', $validated['cotizacion_id'])
        ->where('id_empresa', $user->id_empresa)
        ->update(['estado' => 'aprobada']);
}

Tablas de Base de Datos

TablaDescripciónCampos Clave
ventasEncabezado del documentoid_venta, serie, numero, id_cliente, total, estado_sunat
detalle_ventasProductos de la ventaid_venta, id_producto, cantidad, precio_unitario, total
dias_ventasCuotas de pago a créditoid_venta, monto, fecha, estado (P/C/V)
ventas_pagosMétodos de pago usadosid_venta, id_tipo_pago, monto, voucher
movimientos_stockTrazabilidad de inventarioid_producto, tipo_movimiento, cantidad, stock_anterior, stock_nuevo
ventas_anuladasHistorial de anulacionesid_venta, motivo_anulacion, fecha_anulacion, total_anulado
Importante: Todas las consultas deben incluir el filtro id_empresa para el multi-tenancy del sistema.

Endpoints API

Ver la referencia completa de API:

Listar Ventas

GET /api/ventas

Crear Venta

POST /api/ventas

Ver Detalle

GET /api/ventas/:id

Anular Venta

POST /api/ventas/:id/anular

Próximos Pasos

Enviar a SUNAT

Aprende cómo enviar comprobantes a SUNAT

Notas de Crédito

Emite notas de crédito y débito

Primera Venta

Guía paso a paso para tu primera venta

Reportes

Genera reportes de ventas en Excel y PDF

Build docs developers (and LLMs) love