Files
Autoparts-DB/docs/API-POS.md
2026-04-02 03:38:10 +00:00

1433 lines
25 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"}
```