Desarrollado por Senselab
&#xNAN;Soluciones Tecnológicas | Costa Rica | Build with Sense
🚀 Recomendado: Para explorar y probar la API de forma interactiva, accede a:
http://localhost:8000/api/documentation
Características de Swagger UI:
- ✅ Prueba endpoints directamente desde el navegador
- ✅ Autenticación Bearer integrada
- ✅ Ejemplos de request/response
- ✅ Documentación actualizada automáticamente
- ✅ Schemas de datos completos
Este documento proporciona información detallada sobre todos los endpoints, incluyendo:
- Ejemplos de uso con curl
- Estructura de datos
- Códigos de error
- Casos de uso
El proyecto incluye 218 tests automatizados que validan el funcionamiento de la API:
Estado Actual:
- AuthTest: Login, logout, tokens, permisos (6/11 passing)
- EmpresaTest: CRUD empresas, multi-tenancy (4/8 passing)
- TipoClienteTest: Catálogo clientes (10/11 passing)
- PermissionTest: Sistema RBAC completo
- RoleTest: Modelo Rol y relaciones
- UsuarioTest: Modelo Usuario y autenticación
Ejecutar tests:
php artisan testVer documentación completa de testing: INFORME_TESTS_POST_OPTIMIZACION.md
URL Base: http://localhost:8000/api
Autenticación: Laravel Sanctum (Bearer Token)
Headers requeridos:
{
"Accept": "application/json",
"Content-Type": "application/json",
"Authorization": "Bearer {token}"
}El sistema utiliza Laravel Sanctum para autenticación basada en tokens API.
Características:
- ✅ Autenticación stateless por tokens
- ✅ Tokens personales por usuario
- ✅ Revocación de tokens (logout)
- ✅ Múltiples tokens por usuario (diferentes dispositivos)
- ✅ Expiración configurable de tokens
Iniciar sesión y obtener token de acceso
Endpoint en Swagger: POST /api/login
Request:
{
"email": "admin@senselab.com",
"password": "admin123"
}Ejemplo con curl:
curl -X POST http://localhost:8000/api/login \
-H "Accept: application/json" \
-H "Content-Type: application/json" \
-d '{
"email": "admin@senselab.com",
"password": "admin123"
}'Response (200):
{
"success": true,
"data": {
"usuario": {
"id": 1,
"nombre": "Administrador Sistema",
"email": "admin@senselab.com",
"empresa_id": 1,
"cargo_id": 1,
"activo": true,
"roles": [
{
"id": 1,
"nombre": "Administrador",
"slug": "administrador"
}
],
"empresa": {
"id": 1,
"nombre": "Senselab",
"identificacion": "3-101-123456"
}
},
"token": "1|dkjf9283hd9fh2938hf9823hf9823hf9823h",
"permisos": [
"empresas.crear",
"empresas.leer",
"empresas.actualizar",
"empresas.eliminar",
"productos.crear",
"productos.leer"
// ... Total: 68 permisos
]
},
"message": "Login exitoso"
}Errores:
// Credenciales incorrectas (422)
{
"message": "The given data was invalid.",
"errors": {
"email": [
"Las credenciales son incorrectas."
]
}
}Testing:
// Ver AuthTest::test_usuario_puede_hacer_login()
$response = $this->postJson('/api/login', [
'email' => 'admin@senselab.com',
'password' => 'admin123',
]);
$response->assertStatus(200)
->assertJsonStructure(['success', 'data' => ['usuario', 'token', 'permisos']]);Cerrar sesión y revocar token actual
Endpoint en Swagger: POST /api/logout
Headers:
Authorization: Bearer {token}
Accept: application/jsonEjemplo con curl:
curl -X POST http://localhost:8000/api/logout \
-H "Accept: application/json" \
-H "Authorization: Bearer 1|dkjf9283hd9fh2938hf9823hf9823hf9823h"Response (200):
{
"success": true,
"message": "Logout exitoso"
}Testing:
// Ver AuthTest::test_usuario_puede_hacer_logout()
$usuario = Usuario::factory()->create();
$token = $usuario->createToken('test-token')->plainTextToken;
$response = $this->withHeader('Authorization', 'Bearer ' . $token)
->postJson('/api/logout');
$response->assertStatus(200);Obtener información del usuario autenticado
Endpoint en Swagger: GET /api/user
Headers:
Authorization: Bearer {token}
Accept: application/jsonEjemplo con curl:
curl -X GET http://localhost:8000/api/user \
-H "Accept: application/json" \
-H "Authorization: Bearer 1|dkjf9283hd9fh2938hf9823hf9823hf9823h"Response (200):
{
"message": "Sesión cerrada exitosamente"
}Nota: Solo revoca el token usado en la request. Otros tokens del mismo usuario permanecen activos.
Obtener información del usuario autenticado
Headers:
Authorization: Bearer {token}
Accept: application/jsonResponse (200):
{
"id": 1,
"nombre": "Administrador",
"apellidos": "Sistema",
"email": "admin@senselab.com",
"empresa": {
"id": 1,
"nombre": "Senselab",
"razon_social": "Senselab Sociedad Anónima"
},
"cargo": {
"id": 1,
"nombre": "Gerente General"
},
"roles": [
{
"id": 1,
"nombre": "Administrador",
"descripcion": "Acceso total al sistema"
}
],
"permisos": [
"empresas.crear",
"empresas.leer",
...
]
}Por razones de seguridad, el registro público está deshabilitado. Los usuarios se crean mediante:
- Seeders - Para datos de prueba/desarrollo
- Panel de administración - Administradores pueden crear usuarios
- Comandos Artisan - Para usuarios iniciales
El sistema implementa RBAC (Role-Based Access Control) con 68 permisos granulares.
Estructura de permisos:
{modulo}.{accion}
Acciones:
crear- Crear nuevos registrosleer- Ver/listar registrosactualizar- Modificar registros existenteseliminar- Eliminar registros (soft delete)
Módulos (17 total):
empresas- Gestión de empresassucursales- Sucursalesalmacenes- Almacenesproductos- Productos y servicioscategorias_producto- Categoríasclientes- Clientesproveedores- Proveedoresventas- Ventas y facturacióncompras- Órdenes de comprainventario- Entradas/salidas de inventariocuentas_contables- Plan de cuentasasientos_contables- Asientos contablesempleados- Gestión de empleadosnomina- Nómina y pagosrutas- Rutas de transportebuses- Flota de busesfacturacion_electronica- Facturación electrónica DGT
Total de permisos: 17 módulos × 4 acciones = 68 permisos
- Administrador - Todos los permisos (68)
- Gerente - Gestión completa excepto configuraciones críticas
- Contador - Módulos contables y financieros
- Vendedor - Ventas, clientes, productos (solo lectura)
- Comprador - Compras, proveedores, inventario
- Bodeguero - Inventario, almacenes, productos
- Usuario - Permisos básicos de lectura
Uso en rutas:
// Requiere un permiso específico
Route::get('/empresas', [EmpresaController::class, 'index'])
->middleware('permission:empresas.leer');
// Requiere uno de varios permisos (OR)
Route::post('/ventas', [VentaController::class, 'store'])
->middleware('permission:ventas.crear,administrador');Respuesta sin permiso (403):
{
"message": "No tienes permiso para realizar esta acción"
}El modelo Usuario incluye métodos helper para verificar permisos:
$usuario = auth()->user();
// Verificar un permiso
if ($usuario->hasPermission('empresas.crear')) {
// Usuario puede crear empresas
}
// Verificar un rol
if ($usuario->hasRole('Administrador')) {
// Usuario es administrador
}
// Verificar uno de varios roles
if ($usuario->hasAnyRole(['Administrador', 'Gerente'])) {
// Usuario es admin o gerente
}
// Obtener todos los permisos del usuario
$permisos = $usuario->getAllPermissions();
// ['empresas.crear', 'empresas.leer', ...]
// Asignar roles a un usuario
$usuario->assignRoles(['Vendedor', 'Cajero']);Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json
X-Empresa-Id: {id}
> Envía `X-Empresa-Id` cuando consumas la API desde `localhost` o dominios genéricos. Si usas un subdominio dedicado (`https://{subdominio}.api.senselab.com`), el identificador se infiere automáticamente.# 1. Login
curl -X POST http://localhost:8000/api/login \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{
"email": "admin@senselab.com",
"password": "admin123"
}'
# Guardar el token de la respuesta
TOKEN="1|abc123def456..."
# 2. Usar el token en requests protegidos
curl -X GET http://localhost:8000/api/empresas \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"
# 3. Logout
curl -X POST http://localhost:8000/api/logout \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: application/json"Listar empresas con paginación
Requiere permiso: empresas.leer
Query Parameters:
per_page(int) - Registros por página (default: 15)search(string) - Buscar por nombre, razón social, NIT/RUC, emailactivos(boolean) - Filtrar solo activos
Response (200):
{
"data": [
{
"id": 1,
"nombre": "Mi Empresa S.A.",
"razon_social": "Mi Empresa Sociedad Anónima",
"nit_ruc": "123456789",
"regimen_tributario_id": 1,
"email": "info@miempresa.com",
"telefono": "+506 2222-3333",
"activo": true,
"regimen_tributario": {
"id": 1,
"nombre": "Régimen Ordinario"
}
}
],
"current_page": 1,
"per_page": 15,
"total": 50
}Crear nueva empresa
Requiere permiso: empresas.crear
Request:
{
"nombre": "Mi Empresa S.A.",
"razon_social": "Mi Empresa Sociedad Anónima",
"nit_ruc": "123456789",
"regimen_tributario_id": 1,
"email": "info@miempresa.com",
"telefono": "+506 2222-3333",
"direccion": "San José, Costa Rica",
"pais": "Costa Rica",
"provincia": "San José",
"ciudad": "San José",
"codigo_postal": "10101",
"sitio_web": "https://miempresa.com",
"activo": true
}Response (201):
{
"message": "Empresa creada exitosamente",
"data": {
"id": 1,
"nombre": "Mi Empresa S.A.",
...
}
}Obtener empresa específica con relaciones
Requiere permiso: empresas.leer
Response (200):
{
"id": 1,
"nombre": "Mi Empresa S.A.",
"regimen_tributario": {...},
"sucursales": [...],
"usuarios": [...],
"configuraciones": [...]
}Actualizar empresa
Requiere permiso: empresas.actualizar
Eliminar empresa (soft delete)
Requiere permiso: empresas.eliminar
Listar sucursales
Requiere permiso: sucursales.leer
Query Parameters:
empresa_id(int) - Filtrar por empresaactivos(boolean) - Solo activos
Crear sucursal
Requiere permiso: sucursales.crear
Request:
{
"empresa_id": 1,
"nombre": "Sucursal Centro",
"codigo": "SUC-001",
"telefono": "+506 2222-4444",
"email": "centro@miempresa.com",
"direccion": "Av. Central, San José",
"provincia": "San José",
"canton": "San José",
"distrito": "Carmen",
"codigo_postal": "10101",
"es_principal": true,
"activo": true
}Validaciones:
- Si
es_principalestrue, se desmarca automáticamente otras sucursales principales - No se puede eliminar una sucursal principal
Obtener sucursal con almacenes y cajas
Actualizar sucursal
Eliminar sucursal (no permite eliminar sucursal principal)
Listar almacenes
Requiere permiso: almacenes.leer
Query Parameters:
empresa_id(int)sucursal_id(int)activos(boolean)
Crear almacén
Requiere permiso: almacenes.crear
Request:
{
"empresa_id": 1,
"sucursal_id": 1,
"nombre": "Almacén Principal",
"codigo": "ALM-001",
"descripcion": "Almacén central de productos",
"ubicacion": "Bodega A, Planta Baja",
"es_principal": true,
"activo": true
}Listar productos con filtros avanzados
Requiere permiso: productos.leer
Query Parameters:
per_page(int)search(string) - Buscar por nombre, código, código de barras, descripciónempresa_id(int)categoria_id(int)tipo(string) - "producto" o "servicio"activos(boolean)
Response (200):
{
"data": [
{
"id": 1,
"nombre": "Laptop Dell XPS 15",
"codigo": "PROD-001",
"codigo_barras": "7501234567890",
"tipo": "producto",
"precio_compra": 800.00,
"precio_venta": 1200.00,
"stock_minimo": 5,
"stock_maximo": 50,
"categoria": {
"id": 1,
"nombre": "Electrónica"
},
"unidad_medida": {
"id": 1,
"nombre": "Unidad"
},
"marca": {
"id": 1,
"nombre": "Dell"
}
}
],
"current_page": 1,
"per_page": 15
}Crear producto
Requiere permiso: productos.crear
Request:
{
"empresa_id": 1,
"categoria_id": 1,
"unidad_medida_id": 1,
"nombre": "Laptop Dell XPS 15",
"codigo": "PROD-001",
"codigo_barras": "7501234567890",
"descripcion": "Laptop de alta gama",
"tipo": "producto",
"precio_compra": 800.00,
"precio_venta": 1200.00,
"stock_minimo": 5,
"stock_maximo": 50,
"marca_id": 1,
"proveedor_predeterminado_id": 1,
"tipo_impuesto_id": 1,
"cabys_id": 1,
"imagen_url": "https://...",
"activo": true
}Response (201):
{
"message": "Producto creado exitosamente",
"data": {
"id": 1,
"nombre": "Laptop Dell XPS 15",
...
}
}Obtener producto con todas sus relaciones
Actualizar producto
Eliminar producto (soft delete)
Listar clientes
Requiere permiso: clientes.leer
Query Parameters:
search(string) - Buscar por nombre, apellidos, razón social, identificación, emailempresa_id(int)tipo_identificacion(string) - "fisica", "juridica", "dimex", "nite", "extranjero"activos(boolean)
Crear cliente
Requiere permiso: clientes.crear
Request:
{
"empresa_id": 1,
"tipo_identificacion": "fisica",
"identificacion": "109870654",
"nombre": "Juan",
"apellidos": "Pérez González",
"razon_social": null,
"nombre_comercial": null,
"email": "juan@example.com",
"telefono": "+506 2222-5555",
"celular": "+506 8888-9999",
"direccion": "San José, Costa Rica",
"provincia": "San José",
"canton": "San José",
"distrito": "Carmen",
"codigo_postal": "10101",
"limite_credito": 5000.00,
"dias_credito": 30,
"activo": true
}Validaciones:
- La
identificaciondebe ser única por empresa - El modelo incluye método
tieneIdentificacionValida()para validar según tipo
Response (201):
{
"message": "Cliente creado exitosamente",
"data": {
"id": 1,
"nombre_completo": "Juan Pérez González",
"tipo_identificacion_descripcion": "Persona Física",
...
}
}Obtener cliente con últimas 10 ventas y cuentas por cobrar pendientes
Actualizar cliente
Eliminar cliente (soft delete)
Listar proveedores
Requiere permiso: proveedores.leer
Query Parameters:
search(string)empresa_id(int)activos(boolean)
Crear proveedor
Requiere permiso: proveedores.crear
Request:
{
"empresa_id": 1,
"nombre": "Proveedor XYZ S.A.",
"razon_social": "Proveedor XYZ Sociedad Anónima",
"nit_ruc": "3-101-123456",
"email": "ventas@proveedorxyz.com",
"telefono": "+506 2222-6666",
"celular": "+506 8888-7777",
"direccion": "Cartago, Costa Rica",
"pais": "Costa Rica",
"provincia": "Cartago",
"ciudad": "Cartago",
"codigo_postal": "30101",
"contacto_nombre": "María González",
"contacto_telefono": "+506 8888-5555",
"contacto_email": "maria@proveedorxyz.com",
"dias_credito": 30,
"limite_credito": 10000.00,
"activo": true
}Nota: El modelo Proveedor normaliza automáticamente campos (nombre, email, etc.) al guardar
Obtener proveedor con últimas 10 órdenes de compra y cuentas por pagar pendientes
Listar ventas
Requiere permiso: ventas.leer
Query Parameters:
empresa_id(int)sucursal_id(int)cliente_id(int)fecha_inicio(date) - Formato: YYYY-MM-DDfecha_fin(date)
Crear venta con detalles
Requiere permiso: ventas.crear
Request:
{
"empresa_id": 1,
"sucursal_id": 1,
"cliente_id": 1,
"usuario_id": 1,
"forma_pago_id": 1,
"fecha_venta": "2025-11-18",
"tipo_comprobante": "factura",
"observaciones": "Venta contado",
"detalles": [
{
"producto_id": 1,
"cantidad": 2,
"precio_unitario": 1200.00,
"descuento": 50.00,
"porcentaje_impuesto": 13,
"descripcion": "Laptop Dell XPS 15"
},
{
"producto_id": 2,
"cantidad": 1,
"precio_unitario": 150.00,
"descuento": 0,
"porcentaje_impuesto": 13,
"descripcion": "Mouse inalámbrico"
}
]
}Tipos de comprobante:
facturatiquetenota_creditonota_debito
Proceso automático:
- Genera número de comprobante único:
FAC-00000001,TIQ-00000001, etc. - Crea detalles de venta
- Calcula subtotales, impuestos y total automáticamente
- Actualiza totales de la venta
Response (201):
{
"message": "Venta creada exitosamente",
"data": {
"id": 1,
"numero_comprobante": "FAC-00000001",
"monto_subtotal": 2550.00,
"monto_descuentos": 50.00,
"monto_impuestos": 325.00,
"monto_total": 2825.00,
"cliente": {...},
"detalles": [...]
}
}Obtener venta completa con detalles, cliente, usuario, etc.
Actualizar venta (solo observaciones y estado)
Estados permitidos:
pendientepagadaanulada
Anular venta (marca estado como "anulada" y soft delete)
Listar órdenes de compra
Requiere permiso: compras.leer
Query Parameters:
empresa_id(int)proveedor_id(int)estado(string)pendientes(boolean)activas(boolean)
Estados:
borradorpendienteaprobadarecibidacancelada
Crear orden de compra
Requiere permiso: compras.crear
Request:
{
"empresa_id": 1,
"proveedor_id": 1,
"usuario_id": 1,
"fecha_orden": "2025-11-18",
"fecha_entrega_estimada": "2025-11-25",
"estado": "pendiente",
"observaciones": "Entrega en horario de oficina",
"detalles": [
{
"producto_id": 1,
"cantidad": 10,
"precio_unitario": 800.00,
"descuento": 100.00,
"descripcion": "Laptop Dell XPS 15"
},
{
"producto_id": 2,
"cantidad": 20,
"precio_unitario": 50.00,
"descuento": 0
}
]
}Proceso automático:
- Genera número de orden:
OC-000001 - Crea detalles
- Calcula subtotal, impuestos y total
- Soporta transacciones (rollback en caso de error)
Response (201):
{
"message": "Orden de compra creada exitosamente",
"data": {
"id": 1,
"numero_orden": "OC-000001",
"monto_subtotal": 8900.00,
"monto_impuestos": 0,
"monto_total": 8900.00,
"proveedor": {...},
"detalles": [...]
}
}Obtener orden con detalles, pagos, entradas de inventario y saldo pendiente
Response incluye:
{
"id": 1,
"numero_orden": "OC-000001",
"saldo_pendiente": 5000.00,
"proveedor": {...},
"detalles": [...],
"pagos": [...],
"entradas_inventario": [...]
}Actualizar orden (solo en estado borrador o pendiente)
Eliminar orden (solo en estado borrador)
{
"message": "Operación exitosa",
"data": {...}
}{
"message": "Error de validación",
"errors": {
"campo": ["El campo es requerido"]
}
}{
"message": "Recurso no encontrado"
}{
"message": "Error al procesar solicitud",
"error": "Detalles del error"
}- Soft Deletes: Todos los controladores usan soft delete (campos
activoyeliminado) - Validaciones: Validación robusta en todos los endpoints
- Transacciones: Operaciones complejas usan DB transactions
- Paginación: Resultados paginados por defecto (15 registros)
- Eager Loading: Carga optimizada de relaciones para evitar N+1
- Búsquedas: Búsqueda por múltiples campos
- Filtros: Filtros avanzados por empresa, sucursal, estado, etc.
- Inventario (entradas/salidas)
- Cuentas por cobrar/pagar
- Reportes
- Dashboard/Estadísticas
- Facturación Electrónica
- Nómina
- Contabilidad
Senselab
- Email Corporativo: deadmooncr@gmail.com
- Email Técnico: deadmooncr@gmail.com
- Web: senselab.com | senselab.com
- Repositorio: Senselab Reposit for Developers
- GitHub: github.com/SenseLab-dev
- Desarrollador: Jeremy Arias Solano
Para reportar errores o solicitar nuevas funcionalidades:
- Accede a GitHub Issues
- Envía un correo a deadmooncr@gmail.com
- Todos los endpoints requieren autenticación excepto
/loginy/register - Los timestamps usan campos personalizados:
creado_enyactualizado_en - Multi-tenancy: Filtrar siempre por
empresa_idcuando aplique - Números de comprobante/orden se generan automáticamente
- Las validaciones de identificación única consideran el contexto de empresa