# 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 `. --- ## 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:** ```json { "tenant_id": 1, "pin": "1234", "device_id": "tablet-01", "branch_id": 1 } ``` **Response 200:** ```json { "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:** ```json { "data": [ {"id": 1, "name": "Admin", "initials": "AD", "role": "owner", "role_label": "Dueno"} ] } ``` ### GET /me Info del empleado actual desde el token. **Response 200:** ```json { "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:** ```json { "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:** ```json { "name": "Sucursal Norte", "address": "Blvd. Independencia 200", "phone": "555-0002" } ``` **Response 201:** ```json {"id": 2, "message": "Branch created"} ``` ### GET /employees Lista empleados con detalle. **Permiso:** `config.view` **Response 200:** ```json { "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:** ```json { "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:** ```json {"id": 2, "message": "Employee created"} ``` ### GET /theme Obtener tema actual del tenant. **Permiso:** cualquier empleado autenticado **Response 200:** ```json { "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 barcode - `page` (int, default 1) - `per_page` (int, default 50, max 200) - `branch_id` (int) -- filtrar por sucursal - `category_id` (int) -- filtrar por categoria - `low_stock` (bool) -- solo productos bajo minimo **Response 200:** ```json { "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:** ```json { "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:** ```json {"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:** ```json { "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:** ```json { "inventory_id": 1, "quantity": -2, "reason": "Merma por dano en almacen" } ``` ### POST /transfer Transferir stock entre sucursales. **Permiso:** `inventory.transfer` **Request body:** ```json { "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:** ```json { "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:** ```json { "branch_id": 1, "items": [ {"inventory_id": 1, "counted_quantity": 22} ] } ``` **Response 201:** ```json {"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:** ```json { "count_id": 1 } ``` ### GET /alerts Obtener alertas de inventario (stock cero, bajo minimo, sobre maximo). **Permiso:** `inventory.view` **Response 200:** ```json { "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:** ```json { "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:** ```json {"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:** ```json { "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:** ```json { "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:** ```json { "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: ```json [ {"method": "efectivo", "amount": 500.00}, {"method": "tarjeta", "amount": 312.00, "reference": "AUTH-12345"} ] ``` **Response 201:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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 social - `page` (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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "register_number": 1, "opening_amount": 2000.00 } ``` **Response 201:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "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:** ```json { "register_id": 1, "counted_amount": 4980.00 } ``` **Response 200:** ```json { "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:** ```json { "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:** ```json { "cfdi_id": 1, "uuid": null, "status": "pending", "xml_preview": "