Saltar a contenido

Apéndice N — Cajas registradoras / Z report

N.1. Modelo de integración

POS físico (EPSON TM-T88V, NCR P15, Bematech) → HTTP → Django ERP.

N.2. Endpoint receptor

@csrf_exempt
@require_POST
def caja_evento(request):
    api_key = request.META.get("HTTP_X_CAJA_KEY")
    caja = CajaFiscal.objects.filter(api_key=api_key, activo=True).first()
    if not caja:
        return JsonResponse({"error": "unauthorized"}, status=401)
    payload = json.loads(request.body)
    tipo = payload.get("tipo")
    if tipo == "venta":
        return _procesar_venta_caja(caja, payload)
    elif tipo == "anulacion":
        return _procesar_anulacion_caja(caja, payload)
    elif tipo == "cierre_z":
        return _procesar_cierre_z(caja, payload)

N.3. Modelo ZReport

class ZReport(models.Model):
    caja = models.ForeignKey(CajaFiscal, on_delete=models.PROTECT)
    fecha = models.DateField(db_index=True)
    secuencia_z = models.IntegerField()
    monto_bruto = models.DecimalField(max_digits=14, decimal_places=2)
    monto_itbis = models.DecimalField(max_digits=14, decimal_places=2)
    transacciones_count = models.IntegerField()
    payload_raw = models.JSONField()
    conciliado = models.BooleanField(default=False)
    diferencia = models.DecimalField(max_digits=14, decimal_places=2, default=0)

    class Meta:
        unique_together = [("caja", "fecha", "secuencia_z")]

N.4. Conciliación diaria

Tarea Celery que compara ZReport.monto_bruto vs sum(SaleInvoice.total del día) y alerta diferencias.

Buena práctica: caja en VLAN aislada, salida HTTP solo al endpoint del ERP. Nunca exponer la caja a Internet directo.