- 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
1583 lines
27 KiB
Markdown
1583 lines
27 KiB
Markdown
# 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:**
|
|
```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": "<?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:**
|
|
```json
|
|
{
|
|
"motivo": "02",
|
|
"folio_sustitucion": null
|
|
}
|
|
```
|
|
|
|
Motivos SAT:
|
|
- `01`: Comprobante emitido con errores con relacion (requiere `folio_sustitucion`)
|
|
- `02`: Comprobante emitido con errores sin relacion
|
|
- `03`: No se llevo a cabo la operacion
|
|
- `04`: 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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"year": 2026,
|
|
"month": 3
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 10. Health Check (1 endpoint)
|
|
|
|
### GET /pos/health
|
|
|
|
No requiere autenticacion.
|
|
|
|
**Response 200:**
|
|
```json
|
|
{"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:
|
|
```json
|
|
{"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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:**
|
|
```json
|
|
{
|
|
"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.
|