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.