- API-POS.md: added sections 11-15 (BNPL, ERP, WhatsApp Cloud, Supplier Portal, Dashboard Stats) - ARCHITECTURE.md: added infra/monitoring section with systemd, Prometheus/Grafana, PWA, and integration stubs - INSTALACION.md: added systemd setup, monitoring docker compose, PWA install notes, and Playwright test commands - README.md: updated endpoint count, tech stack, infrastructure table
27 KiB
API Reference -- Nexus Autoparts POS
Base URL: http://[IP]:5001
Todas las rutas empiezan con /pos/api/. Salvo indicacion contraria, todos los endpoints requieren autenticacion via header Authorization: Bearer <token>.
Autenticacion
Todos los endpoints (excepto login y employees list) requieren un JWT token obtenido via /pos/api/auth/login. El token se envia en el header:
Authorization: Bearer eyJhbGciOi...
El token contiene: tenant_id, employee_id, name, role, branch_id, permissions, device_id.
Duracion por defecto: 8 horas.
1. Auth (3 endpoints)
Prefix: /pos/api/auth
POST /login
Login con PIN. No requiere autenticacion.
Request body:
{
"tenant_id": 1,
"pin": "1234",
"device_id": "tablet-01",
"branch_id": 1
}
Response 200:
{
"token": "eyJhbGciOi...",
"employee": {
"id": 1,
"name": "Admin",
"role": "owner",
"branch_id": 1,
"max_discount_pct": 100.0
},
"permissions": ["pos.sell", "pos.discount", "..."]
}
Errores: 400 (faltan campos), 401 (PIN incorrecto), 404 (tenant no encontrado), 429 (rate limit)
GET /employees/:tenant_id
Lista empleados para la pantalla de login. No requiere autenticacion.
Response 200:
{
"data": [
{"id": 1, "name": "Admin", "initials": "AD", "role": "owner", "role_label": "Dueno"}
]
}
GET /me
Info del empleado actual desde el token.
Response 200:
{
"employee_id": 1,
"name": "Admin",
"role": "owner",
"tenant_id": 1,
"branch_id": 1,
"permissions": ["pos.sell", "..."]
}
2. Config (5 endpoints)
Prefix: /pos/api/config
GET /branches
Lista sucursales del tenant.
Permiso: cualquier empleado autenticado
Response 200:
{
"data": [
{"id": 1, "name": "Sucursal Centro", "address": "Av. Juarez 100", "phone": "555-0001", "is_active": true}
]
}
POST /branches
Crear sucursal.
Permiso: config.edit
Request body:
{
"name": "Sucursal Norte",
"address": "Blvd. Independencia 200",
"phone": "555-0002"
}
Response 201:
{"id": 2, "message": "Branch created"}
GET /employees
Lista empleados con detalle.
Permiso: config.view
Response 200:
{
"data": [
{
"id": 1, "name": "Admin", "email": null, "phone": null,
"role": "owner", "branch_id": 1, "branch_name": "Sucursal Centro",
"max_discount_pct": 100.0, "is_active": true
}
]
}
POST /employees
Crear empleado.
Permiso: config.edit
Request body:
{
"name": "Juan Lopez",
"role": "cashier",
"pin": "5678",
"email": "juan@correo.com",
"phone": "555-1234",
"branch_id": 1,
"max_discount_pct": 10
}
Roles validos: admin, cashier, warehouse, accountant
Response 201:
{"id": 2, "message": "Employee created"}
GET /theme
Obtener tema actual del tenant.
Permiso: cualquier empleado autenticado
Response 200:
{
"theme": "default",
"variables": {
"--color-primary": "#1a73e8",
"--color-secondary": "#5f6368",
"--color-accent": "#ff6b35",
"--color-bg": "#ffffff",
"--color-surface": "#f8f9fa",
"--color-text": "#202124",
"--color-border": "#dadce0",
"--font-display": "'Sora', sans-serif",
"--font-body": "'Plus Jakarta Sans', sans-serif",
"--font-mono": "'JetBrains Mono', monospace",
"--radius": "8px"
}
}
3. Inventory (22 endpoints)
Prefix: /pos/api/inventory
GET /items
Lista productos con stock. Soporta busqueda y paginacion.
Permiso: inventory.view
Query params:
q(string) -- buscar por nombre, part_number o barcodepage(int, default 1)per_page(int, default 50, max 200)branch_id(int) -- filtrar por sucursalcategory_id(int) -- filtrar por categorialow_stock(bool) -- solo productos bajo minimo
Response 200:
{
"data": [
{
"id": 1, "part_number": "04465-02220", "barcode": "7501234567890",
"name": "Pastillas de freno delanteras", "description": "...",
"branch_id": 1, "branch_name": "Sucursal Centro",
"category_id": 5, "brand": "Toyota", "unit": "PZA",
"cost": 150.00, "price_1": 350.00, "price_2": 300.00, "price_3": 280.00,
"tax_rate": 0.16, "min_stock": 5, "max_stock": 50, "location": "A-3-2",
"image_url": null, "catalog_part_id": 12345, "stock": 23
}
],
"pagination": {"page": 1, "per_page": 50, "total": 1234, "total_pages": 25}
}
GET /items/:item_id
Detalle de un producto con stock e historial de movimientos.
Permiso: inventory.view
POST /items
Crear producto. Genera codigo de barras automaticamente si no se proporciona.
Permiso: inventory.create
Request body:
{
"part_number": "04465-02220",
"name": "Pastillas de freno delanteras",
"description": "Pastillas ceramicas originales Toyota",
"branch_id": 1,
"category_id": 5,
"brand": "Toyota",
"unit": "PZA",
"cost": 150.00,
"price_1": 350.00,
"price_2": 300.00,
"price_3": 280.00,
"tax_rate": 0.16,
"min_stock": 5,
"max_stock": 50,
"location": "A-3-2",
"initial_stock": 10,
"catalog_part_id": 12345
}
Response 201:
{"id": 1, "barcode": "7501234567890", "message": "Item created"}
PUT /items/:item_id
Actualizar datos de un producto.
Permiso: inventory.edit
Request body: Mismos campos que POST (parcial, solo campos a actualizar).
POST /purchase
Registrar compra a proveedor. Incrementa stock.
Permiso: inventory.create
Request body:
{
"supplier": "Distribuidora Central",
"invoice_number": "FAC-001",
"items": [
{"inventory_id": 1, "quantity": 20, "unit_cost": 145.00}
],
"notes": "Compra semanal"
}
POST /adjustment
Ajuste manual de inventario.
Permiso: inventory.adjust
Request body:
{
"inventory_id": 1,
"quantity": -2,
"reason": "Merma por dano en almacen"
}
POST /transfer
Transferir stock entre sucursales.
Permiso: inventory.transfer
Request body:
{
"from_branch_id": 1,
"to_branch_id": 2,
"items": [
{"inventory_id": 1, "quantity": 5}
],
"notes": "Reabasto sucursal norte"
}
POST /return
Registrar devolucion de producto. Incrementa stock.
Permiso: inventory.create
Request body:
{
"sale_id": 123,
"items": [
{"inventory_id": 1, "quantity": 1}
],
"reason": "Parte equivocada"
}
POST /physical-count/start
Iniciar toma fisica de inventario.
Permiso: inventory.adjust
Request body:
{
"branch_id": 1,
"items": [
{"inventory_id": 1, "counted_quantity": 22}
]
}
Response 201:
{"count_id": 1, "items_counted": 1, "message": "Physical count started"}
POST /physical-count/approve
Aprobar toma fisica y aplicar ajustes.
Permiso: inventory.adjust
Request body:
{
"count_id": 1
}
GET /alerts
Obtener alertas de inventario (stock cero, bajo minimo, sobre maximo).
Permiso: inventory.view
Response 200:
{
"zero_stock": [...],
"low_stock": [...],
"over_stock": [...]
}
GET /items/:item_id/history
Historial de movimientos de un producto.
Permiso: inventory.view
Query params:
limit(int, default 50)
GET /reports/valuation
Reporte de valorizacion del inventario.
Permiso: inventory.view
Query params:
branch_id(int, opcional)
Response 200:
{
"total_cost_value": 125000.00,
"total_sale_value": 350000.00,
"item_count": 450,
"items": [...]
}
GET /reports/abc
Clasificacion ABC de productos por volumen de venta.
Permiso: inventory.view
Query params:
branch_id(int, opcional)days(int, default 90)
GET /reports/no-movement
Productos sin movimiento en un periodo.
Permiso: inventory.view
Query params:
branch_id(int, opcional)days(int, default 60)
GET /reports/low-stock
Productos por debajo del stock minimo.
Permiso: inventory.view
Query params:
branch_id(int, opcional)
GET /reports/branch-comparison
Comparativo de stock entre sucursales.
Permiso: inventory.view
Query params:
inventory_id(int, opcional) -- para un producto especifico
GET /categories
Lista categorias de inventario.
Permiso: inventory.view
GET /brands
Lista marcas de productos en inventario.
Permiso: inventory.view
POST /generate-barcode
Generar codigo de barras para un producto.
Permiso: inventory.create
Response 200:
{"barcode": "7501234567891"}
4. Catalog (9 endpoints)
Prefix: /pos/api/catalog
Estos endpoints consultan la base maestra TecDoc y enriquecen con stock local del tenant.
GET /brands
Lista marcas de vehiculos con partes.
Permiso: catalog.view
Response 200:
{
"data": [
{"id": 1, "name": "TOYOTA", "part_count": 245000}
]
}
GET /models
Modelos por marca.
Permiso: catalog.view
Query params:
brand_id(int, requerido)
GET /years
Anos disponibles por modelo.
Permiso: catalog.view
Query params:
model_id(int, requerido)
GET /engines
Motorizaciones por modelo + ano.
Permiso: catalog.view
Query params:
model_id(int, requerido)year_id(int, requerido)
GET /categories
Categorias de partes para un vehiculo.
Permiso: catalog.view
Query params:
mye_id(int, requerido) -- ID de model_year_engine
GET /groups
Subcategorias (grupos) de partes.
Permiso: catalog.view
Query params:
mye_id(int, requerido)category_id(int, requerido)
GET /parts
Partes con enriquecimiento de stock local.
Permiso: catalog.view
Query params:
mye_id(int, requerido)group_id(int, requerido)page(int, default 1)per_page(int, default 30)branch_id(int, opcional)
Response 200:
{
"data": [
{
"id": 12345, "part_number": "04465-02220", "name": "Pastillas de freno",
"manufacturer": "TOYOTA", "local_stock": 5, "local_price": 350.00,
"alternatives_count": 3
}
],
"pagination": {"page": 1, "per_page": 30, "total": 15, "total_pages": 1}
}
GET /part/:part_id
Detalle completo: stock local, stock en bodegas, alternativas aftermarket, cross-references.
Permiso: catalog.view
Query params:
branch_id(int, opcional)
GET /search
Busqueda inteligente por numero de parte o texto.
Permiso: catalog.view
Query params:
q(string, requerido, minimo 2 caracteres)limit(int, default 50)branch_id(int, opcional)
5. POS / Sales (16 endpoints)
Prefix: /pos/api
POST /sales
Crear venta.
Permiso: pos.sell
Request body:
{
"items": [
{"inventory_id": 1, "quantity": 2, "unit_price": 350.00, "discount_pct": 0, "tax_rate": 0.16}
],
"customer_id": null,
"payment_method": "efectivo",
"sale_type": "cash",
"register_id": 1,
"amount_paid": 812.00,
"payment_details": [],
"notes": ""
}
sale_type: cash (contado), credit (credito), mixed
payment_method: efectivo, transferencia, tarjeta, mixto
Para pagos mixtos, payment_details es un array:
[
{"method": "efectivo", "amount": 500.00},
{"method": "tarjeta", "amount": 312.00, "reference": "AUTH-12345"}
]
Response 201:
{
"sale_id": 1,
"folio": "V-0001",
"total": 812.00,
"change": 0.00,
"items_count": 1
}
GET /sales
Lista ventas con filtros.
Permiso: pos.view
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)employee_id(int)customer_id(int)status(string:completed,cancelled,returned)register_id(int)page(int, default 1)per_page(int, default 50, max 200)
GET /sales/:sale_id
Detalle de una venta con items, pagos y datos del cliente.
Permiso: pos.view
PUT /sales/:sale_id/cancel
Cancelar una venta. Restaura inventario.
Permiso: pos.cancel
Request body:
{
"reason": "Cliente cambio de opinion"
}
GET /sales/last
Obtener la ultima venta (para reimprimir ticket).
Permiso: pos.sell
POST /quotations
Crear cotizacion (venta sin cobrar).
Permiso: pos.sell
Request body:
{
"items": [
{"inventory_id": 1, "quantity": 2, "unit_price": 350.00, "discount_pct": 0, "tax_rate": 0.16}
],
"customer_id": 5,
"notes": "Cotizacion para taller Perez",
"valid_days": 15
}
GET /quotations
Lista cotizaciones.
Permiso: pos.view
Query params:
status(string:active,converted,expired,cancelled)customer_id(int)page(int, default 1)per_page(int, default 50)
GET /quotations/:quot_id
Detalle de una cotizacion.
Permiso: pos.view
POST /quotations/:quot_id/convert
Convertir cotizacion a venta. Aplica los mismos campos que POST /sales.
Permiso: pos.sell
Request body:
{
"payment_method": "efectivo",
"register_id": 1,
"amount_paid": 812.00
}
PUT /quotations/:quot_id/cancel
Cancelar cotizacion.
Permiso: pos.cancel
POST /layaways
Crear apartado con pago parcial.
Permiso: pos.sell
Request body:
{
"items": [
{"inventory_id": 1, "quantity": 1, "unit_price": 5000.00, "discount_pct": 0, "tax_rate": 0.16}
],
"customer_id": 5,
"down_payment": 2000.00,
"payment_method": "efectivo",
"register_id": 1,
"notes": "Apartado motor de arranque",
"due_date": "2026-04-30"
}
GET /layaways
Lista apartados.
Permiso: pos.view
Query params:
status(string:active,completed,cancelled)customer_id(int)page,per_page
GET /layaways/:layaway_id
Detalle de un apartado con items y pagos.
Permiso: pos.view
POST /layaways/:layaway_id/payment
Registrar abono a un apartado.
Permiso: pos.sell
Request body:
{
"amount": 1500.00,
"payment_method": "efectivo",
"register_id": 1
}
POST /layaways/:layaway_id/complete
Completar apartado (cuando el saldo llega a cero). Descuenta inventario.
Permiso: pos.sell
PUT /layaways/:layaway_id/cancel
Cancelar apartado.
Permiso: pos.cancel
Request body:
{
"reason": "Cliente no completo pagos"
}
6. Customers (7 endpoints)
Prefix: /pos/api/customers
GET /
Lista clientes con busqueda.
Permiso: customers.view
Query params:
q(string) -- busca por nombre, RFC, telefono, razon socialpage(int, default 1)per_page(int, default 50, max 200)branch_id(int)
GET /:customer_id
Detalle de un cliente.
Permiso: customers.view
POST /
Crear cliente.
Permiso: customers.create
Request body:
{
"name": "Taller Perez",
"rfc": "XAXX010101000",
"razon_social": "Taller Mecanico Perez SA de CV",
"regimen_fiscal": "601",
"codigo_postal": "06600",
"uso_cfdi": "G03",
"email": "taller@correo.com",
"phone": "555-9876",
"address": "Calle Reforma 50",
"price_tier": "taller",
"credit_limit": 50000.00,
"branch_id": 1
}
price_tier: mostrador (default), taller, mayoreo
PUT /:customer_id
Actualizar cliente.
Permiso: customers.edit
GET /:customer_id/statement
Estado de cuenta del cliente.
Permiso: customers.view
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)
Response 200:
{
"customer": {"id": 5, "name": "Taller Perez", "credit_limit": 50000.00, "balance": 12500.00},
"transactions": [
{"date": "2026-03-28", "type": "sale", "reference": "V-0045", "amount": 5000.00, "balance": 12500.00},
{"date": "2026-03-25", "type": "payment", "reference": "PAG-012", "amount": -3000.00, "balance": 7500.00}
]
}
GET /:customer_id/vehicles
Vehiculos asociados al cliente.
Permiso: customers.view
POST /:customer_id/payment
Registrar pago/abono del cliente.
Permiso: customers.edit
Request body:
{
"amount": 5000.00,
"payment_method": "transferencia",
"reference": "SPEI-20260401-001",
"notes": "Abono parcial"
}
7. Cash Register (7 endpoints)
Prefix: /pos/api/register
POST /open
Abrir caja registradora.
Permiso: pos.sell
Request body:
{
"register_number": 1,
"opening_amount": 2000.00
}
Response 201:
{
"register_id": 1,
"register_number": 1,
"opened_at": "2026-04-01T08:00:00",
"opening_amount": 2000.00
}
Errores: 400 (ya tiene caja abierta)
GET /current
Obtener la caja abierta del empleado actual.
Permiso: pos.sell
Response 200:
{
"register_id": 1,
"register_number": 1,
"opened_at": "2026-04-01T08:00:00",
"opening_amount": 2000.00,
"current_cash": 5430.00,
"sales_count": 12,
"sales_total": 8750.00
}
POST /movement
Registrar entrada o salida de efectivo.
Permiso: pos.sell
Request body:
{
"register_id": 1,
"type": "out",
"amount": 500.00,
"reason": "Compra de papeleria"
}
type: in (entrada) o out (salida)
GET /cut-x
Corte X (consulta sin cerrar caja).
Permiso: pos.sell
Response 200:
{
"register_id": 1,
"opening_amount": 2000.00,
"sales_cash": 3500.00,
"sales_card": 2100.00,
"sales_transfer": 1150.00,
"movements_in": 0.00,
"movements_out": 500.00,
"expected_cash": 5000.00,
"sales_count": 12,
"total_sales": 6750.00
}
POST /cut-z
Corte Z (cierre de caja).
Permiso: pos.sell
Request body:
{
"register_id": 1,
"counted_amount": 4980.00
}
Response 200:
{
"register_id": 1,
"expected_cash": 5000.00,
"counted_amount": 4980.00,
"difference": -20.00,
"closed_at": "2026-04-01T22:00:00",
"summary": {"..."}
}
GET /history
Historial de cortes.
Permiso: pos.sell
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)page,per_page
GET /daily-summary
Resumen consolidado del dia (todas las cajas).
Permiso: pos.sell
Query params:
date(YYYY-MM-DD, default hoy)
8. Invoicing (6 endpoints)
Prefix: /pos/api/invoicing
POST /invoice
Generar factura CFDI 4.0 desde una venta.
Permiso: invoicing.create
Request body:
{
"sale_id": 45,
"tipo_comprobante": "I",
"uso_cfdi": "G03",
"forma_pago": "01",
"metodo_pago": "PUE"
}
tipo_comprobante: I (Ingreso), E (Egreso), P (Pago)
metodo_pago: PUE (Pago en Una sola Exhibicion) o PPD (Pago en Parcialidades o Diferido)
Response 201:
{
"cfdi_id": 1,
"uuid": null,
"status": "pending",
"xml_preview": "<?xml ..."
}
GET /queue
Lista cola de timbrado.
Permiso: invoicing.view
Query params:
status(string:pending,stamped,error,cancelled)page,per_page
GET /queue/:cfdi_id
Detalle de un CFDI en cola.
Permiso: invoicing.view
POST /queue/process
Procesar cola de timbrado (enviar pendientes al PAC).
Permiso: invoicing.create
POST /cancel/:cfdi_id
Cancelar factura ante el SAT.
Permiso: invoicing.cancel
Request body:
{
"motivo": "02",
"folio_sustitucion": null
}
Motivos SAT:
01: Comprobante emitido con errores con relacion (requierefolio_sustitucion)02: Comprobante emitido con errores sin relacion03: No se llevo a cabo la operacion04: Operacion nominativa relacionada en una factura global
GET /:sale_id/pdf
Descargar PDF de representacion impresa de la factura.
Permiso: invoicing.view
Response: PDF file (application/pdf)
9. Accounting (11 endpoints)
Prefix: /pos/api/accounting
GET /accounts
Catalogo de cuentas contables (lista plana, el frontend construye el arbol).
Permiso: accounting.view
Response 200:
{
"data": [
{
"id": 1, "code": "100", "sat_code": "100",
"name": "Activo", "type": "debit",
"parent_id": null, "level": 1, "is_active": true
}
]
}
POST /accounts
Crear cuenta contable.
Permiso: accounting.create
Request body:
{
"code": "100.01",
"sat_code": "100.01",
"name": "Caja",
"type": "debit",
"parent_id": 1,
"description": "Efectivo en caja"
}
GET /entries
Lista polizas contables.
Permiso: accounting.view
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)type(string:sale,purchase,adjustment,manual)page,per_page
GET /entries/:entry_id
Detalle de una poliza con lineas de cargo y abono.
Permiso: accounting.view
Response 200:
{
"id": 1,
"date": "2026-04-01",
"type": "sale",
"reference": "V-0045",
"description": "Venta al contado",
"lines": [
{"account_id": 5, "account_code": "100.01", "account_name": "Caja", "debit": 812.00, "credit": 0.00},
{"account_id": 10, "account_code": "400.01", "account_name": "Ventas", "debit": 0.00, "credit": 700.00},
{"account_id": 15, "account_code": "210.01", "account_name": "IVA Trasladado", "debit": 0.00, "credit": 112.00}
]
}
POST /entries
Crear poliza manual.
Permiso: accounting.create
Request body:
{
"date": "2026-04-01",
"description": "Pago de renta del local",
"lines": [
{"account_id": 20, "debit": 15000.00, "credit": 0.00},
{"account_id": 5, "debit": 0.00, "credit": 15000.00}
]
}
La suma de cargos debe ser igual a la suma de abonos.
GET /trial-balance
Balanza de comprobacion.
Permiso: accounting.view
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)
Response 200:
{
"period": {"from": "2026-04-01", "to": "2026-04-30"},
"accounts": [
{
"code": "100.01", "name": "Caja",
"opening_debit": 50000.00, "opening_credit": 0.00,
"period_debit": 25000.00, "period_credit": 18000.00,
"closing_debit": 57000.00, "closing_credit": 0.00
}
],
"totals": {
"opening_debit": 100000.00, "opening_credit": 100000.00,
"period_debit": 45000.00, "period_credit": 45000.00,
"closing_debit": 145000.00, "closing_credit": 145000.00
}
}
GET /income-statement
Estado de resultados.
Permiso: accounting.view
Query params:
date_from(YYYY-MM-DD)date_to(YYYY-MM-DD)
Response 200:
{
"period": {"from": "2026-04-01", "to": "2026-04-30"},
"income": [{"code": "400.01", "name": "Ventas", "amount": 150000.00}],
"cost_of_sales": [{"code": "500.01", "name": "Costo de Ventas", "amount": 90000.00}],
"expenses": [{"code": "600.01", "name": "Gastos de Operacion", "amount": 25000.00}],
"gross_profit": 60000.00,
"operating_profit": 35000.00,
"net_income": 35000.00
}
GET /balance-sheet
Balance general.
Permiso: accounting.view
Query params:
date(YYYY-MM-DD, default hoy)
Response 200:
{
"date": "2026-04-30",
"assets": [{"code": "100", "name": "Activo Circulante", "amount": 250000.00, "children": [...]}],
"liabilities": [{"code": "200", "name": "Pasivo", "amount": 80000.00, "children": [...]}],
"equity": [{"code": "300", "name": "Capital", "amount": 170000.00, "children": [...]}],
"total_assets": 250000.00,
"total_liabilities_equity": 250000.00
}
GET /aging
Antiguedad de saldos (cuentas por cobrar).
Permiso: accounting.view
Query params:
date(YYYY-MM-DD, default hoy)
Response 200:
{
"date": "2026-04-30",
"customers": [
{
"customer_id": 5, "name": "Taller Perez",
"current": 5000.00,
"days_1_30": 3000.00,
"days_31_60": 2000.00,
"days_61_90": 0.00,
"days_over_90": 0.00,
"total": 10000.00
}
],
"totals": {
"current": 15000.00, "days_1_30": 8000.00,
"days_31_60": 3000.00, "days_61_90": 1000.00,
"days_over_90": 500.00, "total": 27500.00
}
}
GET /periods
Lista periodos fiscales.
Permiso: accounting.view
Response 200:
{
"data": [
{"year": 2026, "month": 4, "status": "open", "closed_at": null, "closed_by": null},
{"year": 2026, "month": 3, "status": "closed", "closed_at": "2026-04-05T10:00:00", "closed_by": "Admin"}
]
}
POST /periods/close
Cerrar periodo fiscal. Irreversible.
Permiso: accounting.create
Request body:
{
"year": 2026,
"month": 3
}
10. Health Check (1 endpoint)
GET /pos/health
No requiere autenticacion.
Response 200:
{"status": "ok"}
Resumen de endpoints
| Modulo | Prefix | Endpoints |
|---|---|---|
| Auth | /pos/api/auth |
3 |
| Config | /pos/api/config |
5 |
| Inventory | /pos/api/inventory |
22 |
| Catalog | /pos/api/catalog |
9 |
| POS/Sales | /pos/api |
16 |
| Customers | /pos/api/customers |
7 |
| Cash Register | /pos/api/register |
7 |
| Invoicing | /pos/api/invoicing |
6 |
| Accounting | /pos/api/accounting |
11 |
| Health | /pos |
1 |
| Total | 87 |
Codigos de error comunes
| Codigo | Significado |
|---|---|
| 400 | Request invalido (faltan campos requeridos, datos incorrectos) |
| 401 | No autenticado (token faltante, expirado o invalido) |
| 403 | Sin permiso (el rol no tiene el permiso requerido) |
| 404 | Recurso no encontrado |
| 429 | Rate limit excedido (demasiados intentos de PIN) |
| 500 | Error interno del servidor |
Todos los errores retornan:
{"error": "Descripcion del error"}
11. BNPL (Buy Now Pay Later) — Stub
Prefix: /pos/api/bnpl
Stubs ready for APLAZO, Kueski, Clip integration.
GET /providers
List configured BNPL providers.
Response 200:
{
"providers": [
{"id": "ap lazo", "name": "APLAZO", "enabled": false, "config_needed": ["api_key", "merchant_id"]},
{"id": "kueski", "name": "Kueski Pay", "enabled": false, "config_needed": ["api_key", "secret"]},
{"id": "clip", "name": "Clip Pagos", "enabled": false, "config_needed": ["api_key"]}
]
}
POST /applications
Create a BNPL application for a sale.
Request body:
{
"sale_id": 123,
"amount": 1500.00,
"provider": "ap lazo",
"customer": {"name": "Juan Perez", "phone": "5512345678"}
}
GET /applications/:id
Get application status.
POST /webhook/:provider
Receive provider webhooks.
12. ERP Sync — Stub
Prefix: /pos/api/erp
Stubs ready for Aspel SAE, CONTPAQi, SAP B1, Odoo.
GET /providers
List supported ERP systems.
POST /sync
Start a sync job.
Request body:
{
"provider": "aspel_sae",
"sync_type": "sales"
}
GET /sync/:job_id
Get sync job status.
POST /sync/:job_id/run
Mock execute sync job.
13. WhatsApp Business API (Meta Cloud) — Stub
Prefix: /pos/api/whatsapp-cloud
GET/POST /webhook
Meta Cloud API webhook verification and message reception.
GET /status
Check Meta Cloud API connection status.
GET /templates
List approved message templates.
POST /messages
Send a message.
Request body:
{
"to": "5215512345678",
"body": "Su orden esta lista",
"template": "order_ready"
}
14. Supplier Portal
Prefix: /pos/api/supplier-portal
GET /demand
Aggregated demand by zone, part group, and time range.
Query params: days (default 30), group_id, branch_id
GET /top-parts
Top 50 moving parts with current stock.
Query params: days (default 30)
15. Dashboard Stats
Prefix: /pos/api/dashboard
GET /stats
Summary stats for today and this month.
Response 200:
{
"today": {"sales_count": 42, "sales_total": 12500.00},
"month": {"sales_count": 1200, "sales_total": 450000.00},
"top_products": [...],
"hourly_sales": [...]
}
GET /stats/employees
Sales per employee today.