Creación de Base de Datos
MySQL Setup
Crear base de datos
CREATE DATABASE factura_santoD
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
Usa utf8mb4 para soportar caracteres especiales y emojis correctamente.
Crear usuario dedicado
CREATE USER ' factura_user '@ 'localhost'
IDENTIFIED BY 'password_seguro_aqui' ;
GRANT ALL PRIVILEGES ON factura_santoD. *
TO 'factura_user' @ 'localhost' ;
FLUSH PRIVILEGES;
Seguridad : En producción, usa contraseñas fuertes y limita privilegios según necesidad.
Verificar creación
SHOW DATABASES;
USE factura_santoD;
SELECT DATABASE ();
Configurar .env
Edita el archivo .env con los datos de conexión:
DB_CONNECTION = mysql
DB_HOST = 127.0.0.1
DB_PORT = 3306
DB_DATABASE = factura_santoD
DB_USERNAME = factura_user
DB_PASSWORD = password_seguro_aqui
Migraciones
Ejecutar Migraciones
Si ejecutaste composer setup, las migraciones ya están aplicadas. Si no:
Salida esperada:
Migration table created successfully.
Migrating: 2026_03_04_000001_create_bancos_table
Migrated: 2026_03_04_000001_create_bancos_table (45.23ms)
Migrating: 2026_03_04_000002_create_metodos_pago_table
Migrated: 2026_03_04_000002_create_metodos_pago_table (52.17ms)
...
Comandos de Migración
Ejecutar migraciones
Rollback último batch
Rollback todas
Fresh (drop all + migrate)
Fresh + seeders
Ver estado
Migrar archivo específico
Migraciones del Sistema
El sistema incluye 29+ migraciones que crean:
Finanzas (17 tablas)
Comercial (5 tablas)
Facturación (6 tablas)
Almacén (4 tablas)
Seguridad (5 tablas)
bancos - Catálogo de bancos peruanos
metodos_pago - Métodos de pago (efectivo, tarjeta, etc.)
denominaciones_billetes - Billetes peruanos (S/200, S/100…)
cajas - Cajas registradoras
movimientos_caja - Ingresos/egresos de caja
apertura_caja_billetes - Detalle billetes en apertura
cierre_caja_billetes - Detalle billetes en cierre
auditoria_cajas - Auditoría de operaciones
permisos_caja - Permisos por usuario
cuentas_bancarias - Cuentas bancarias de empresa
titulares_cuenta_bancaria - Titulares de cuentas
movimientos_bancarios - Transacciones bancarias
conciliaciones_bancarias - Conciliaciones bancarias
billeteras_digitales - Yape, Plin, BIM, etc.
configuracion_metodos_pago - Config de métodos
reportes_financieros - Reportes generados
caja_metodos_pago - Relación caja-métodos
clientes - Clientes (DNI/RUC)
proveedores - Proveedores
transportistas - Transportistas para guías
cotizaciones - Cotizaciones
detalle_cotizaciones - Items de cotizaciones
ventas - Facturas y boletas
detalle_ventas - Items de ventas
dias_ventas - Cuotas de pago (CxC)
guia_remisions - Guías de remisión
detalle_guia_remisions - Items de guías
guia_remision_transportistas - Guías de transportista
productos - Catálogo de productos
movimientos_stock - Trazabilidad de inventario
compras - Compras a proveedores
detalle_compras - Items de compras
dias_compras - Cuotas de pago (CxP)
users - Usuarios del sistema
roles - Roles de usuario
permisos - Catálogo de permisos
rol_permisos - Relación rol-permisos
empresas - Datos de empresa
Estructura de Tablas Principales
Tabla: empresas
CREATE TABLE empresas (
id_empresa INT PRIMARY KEY AUTO_INCREMENT,
ruc VARCHAR ( 11 ) NOT NULL UNIQUE ,
razon_social VARCHAR ( 255 ) NOT NULL ,
nombre_comercial VARCHAR ( 255 ),
direccion TEXT ,
ubigeo VARCHAR ( 6 ),
distrito VARCHAR ( 100 ),
provincia VARCHAR ( 100 ),
departamento VARCHAR ( 100 ),
-- Credenciales SUNAT
user_sol VARCHAR ( 255 ),
clave_sol VARCHAR ( 255 ), -- Encriptada
-- Modo de operación
modo ENUM( 'beta' , 'production' ) DEFAULT 'beta' ,
-- Contacto
telefono VARCHAR ( 20 ),
email VARCHAR ( 255 ),
web VARCHAR ( 255 ),
created_at TIMESTAMP ,
updated_at TIMESTAMP
);
Importante : La columna id_empresa es INT (signed), no UNSIGNED. Todas las FKs deben usar integer() en migraciones.
Tabla: users
CREATE TABLE users (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_empresa INT NOT NULL ,
name VARCHAR ( 255 ) NOT NULL ,
email VARCHAR ( 255 ) NOT NULL UNIQUE ,
password VARCHAR ( 255 ) NOT NULL ,
rol_id INT ,
-- Permisos de caja (por usuario)
puede_abrir_caja BOOLEAN DEFAULT false,
puede_cerrar_caja BOOLEAN DEFAULT false,
puede_autorizar_cierre BOOLEAN DEFAULT false,
puede_registrar_movimientos BOOLEAN DEFAULT false,
puede_ver_reportes BOOLEAN DEFAULT false,
remember_token VARCHAR ( 100 ),
created_at TIMESTAMP ,
updated_at TIMESTAMP ,
FOREIGN KEY (id_empresa) REFERENCES empresas(id_empresa),
FOREIGN KEY (rol_id) REFERENCES roles(id)
);
Tabla: ventas
CREATE TABLE ventas (
id_venta BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_empresa INT NOT NULL ,
id_cliente BIGINT UNSIGNED NOT NULL ,
-- Datos del comprobante
tipo_comprobante ENUM( '01' , '03' , '07' , '08' ), -- 01=Factura, 03=Boleta, 07=NC, 08=ND
serie VARCHAR ( 4 ) NOT NULL , -- F001, B001, FC01, etc.
numero INT NOT NULL ,
fecha_emision DATE NOT NULL ,
fecha_vencimiento DATE ,
-- Montos
subtotal DECIMAL ( 12 , 2 ) NOT NULL ,
igv DECIMAL ( 12 , 2 ) NOT NULL ,
total DECIMAL ( 12 , 2 ) NOT NULL ,
-- SUNAT
estado_sunat ENUM( 'pendiente' , 'aceptado' , 'rechazado' , 'anulado' ),
mensaje_sunat TEXT ,
xml_path VARCHAR ( 255 ),
cdr_path VARCHAR ( 255 ),
hash_cpe VARCHAR ( 255 ),
-- Pago
forma_pago ENUM( 'contado' , 'credito' ) DEFAULT 'contado' ,
estado_pago ENUM( 'P' , 'C' , 'V' ), -- P=Pendiente, C=Cancelado, V=Vencido
created_at TIMESTAMP ,
updated_at TIMESTAMP ,
FOREIGN KEY (id_empresa) REFERENCES empresas(id_empresa),
FOREIGN KEY (id_cliente) REFERENCES clientes(id_cliente),
UNIQUE KEY (id_empresa, serie, numero)
);
Tabla: detalle_ventas
CREATE TABLE detalle_ventas (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_venta BIGINT UNSIGNED NOT NULL ,
id_producto BIGINT UNSIGNED,
codigo VARCHAR ( 50 ),
descripcion TEXT NOT NULL ,
cantidad DECIMAL ( 12 , 4 ) NOT NULL ,
unidad VARCHAR ( 10 ) DEFAULT 'NIU' , -- Código SUNAT
precio_unitario DECIMAL ( 12 , 2 ) NOT NULL ,
descuento DECIMAL ( 12 , 2 ) DEFAULT 0 ,
subtotal DECIMAL ( 12 , 2 ) NOT NULL ,
igv DECIMAL ( 12 , 2 ) NOT NULL ,
total DECIMAL ( 12 , 2 ) NOT NULL ,
FOREIGN KEY (id_venta) REFERENCES ventas(id_venta) ON DELETE CASCADE ,
FOREIGN KEY (id_producto) REFERENCES productos(id_producto)
);
Tabla: dias_ventas (Cuotas CxC)
CREATE TABLE dias_ventas (
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_venta BIGINT UNSIGNED NOT NULL ,
id_empresa INT NOT NULL ,
numero_cuota INT NOT NULL ,
monto DECIMAL ( 12 , 2 ) NOT NULL ,
fecha_vencimiento DATE NOT NULL ,
fecha_pago DATE ,
estado ENUM( 'P' , 'C' , 'V' ) DEFAULT 'P' , -- Pendiente, Cancelado, Vencido
observaciones TEXT ,
created_at TIMESTAMP ,
updated_at TIMESTAMP ,
FOREIGN KEY (id_venta) REFERENCES ventas(id_venta) ON DELETE CASCADE ,
FOREIGN KEY (id_empresa) REFERENCES empresas(id_empresa)
);
Tabla: guia_remisions
CREATE TABLE guia_remisions (
id_guia BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_empresa INT NOT NULL ,
id_cliente BIGINT UNSIGNED,
id_transportista BIGINT UNSIGNED,
-- Comprobante
serie VARCHAR ( 4 ) NOT NULL , -- T001
numero INT NOT NULL ,
fecha_emision DATE NOT NULL ,
-- Envío
fecha_inicio_traslado DATE NOT NULL ,
motivo_traslado VARCHAR ( 2 ) NOT NULL , -- Código SUNAT
modalidad_traslado VARCHAR ( 2 ) NOT NULL , -- 01=Público, 02=Privado
peso_bruto DECIMAL ( 12 , 3 ),
numero_bultos INT ,
-- Dirección origen
direccion_origen TEXT ,
ubigeo_origen VARCHAR ( 6 ),
-- Dirección destino
direccion_destino TEXT ,
ubigeo_destino VARCHAR ( 6 ),
-- Remitente (nuevo)
remitente_tipo_doc VARCHAR ( 1 ),
remitente_num_doc VARCHAR ( 15 ),
remitente_razon_social VARCHAR ( 255 ),
remitente_direccion TEXT ,
remitente_ubigeo VARCHAR ( 6 ),
-- Transporte
conductor_nombre VARCHAR ( 255 ),
conductor_documento VARCHAR ( 20 ),
conductor_licencia VARCHAR ( 20 ),
placa_vehiculo VARCHAR ( 10 ),
placa_secundaria VARCHAR ( 10 ), -- Remolque/carreta
-- SUNAT (GRE API)
ticket VARCHAR ( 255 ), -- Ticket async
estado_sunat ENUM( 'pendiente' , 'proceso' , 'aceptado' , 'rechazado' ),
mensaje_sunat TEXT ,
xml_path VARCHAR ( 255 ),
cdr_path VARCHAR ( 255 ),
created_at TIMESTAMP ,
updated_at TIMESTAMP ,
FOREIGN KEY (id_empresa) REFERENCES empresas(id_empresa),
FOREIGN KEY (id_cliente) REFERENCES clientes(id_cliente),
FOREIGN KEY (id_transportista) REFERENCES transportistas(id_transportista),
UNIQUE KEY (id_empresa, serie, numero)
);
Tabla: cajas
CREATE TABLE cajas (
id_caja BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
id_empresa INT NOT NULL ,
nombre VARCHAR ( 255 ), -- "Caja Principal", "Caja 2", etc.
responsable VARCHAR ( 255 ),
id_usuario_apertura BIGINT UNSIGNED,
id_usuario_cierre BIGINT UNSIGNED,
id_usuario_autoriza_cierre BIGINT UNSIGNED,
fecha_apertura DATETIME NOT NULL ,
fecha_cierre DATETIME ,
fecha_autorizacion_cierre DATETIME ,
saldo_inicial DECIMAL ( 12 , 2 ) DEFAULT 0 ,
saldo_final_teorico DECIMAL ( 12 , 2 ),
saldo_final_real DECIMAL ( 12 , 2 ),
diferencia DECIMAL ( 12 , 2 ),
estado ENUM( 'Abierta' , 'Cerrada' , 'Pendiente Autorización' , 'Inactiva' ) DEFAULT 'Abierta' ,
observaciones TEXT ,
observaciones_cierre TEXT ,
created_at TIMESTAMP ,
updated_at TIMESTAMP ,
FOREIGN KEY (id_empresa) REFERENCES empresas(id_empresa),
FOREIGN KEY (id_usuario_apertura) REFERENCES users(id),
FOREIGN KEY (id_usuario_cierre) REFERENCES users(id),
FOREIGN KEY (id_usuario_autoriza_cierre) REFERENCES users(id)
);
Seeders
Ejecutar Seeders
Seeders incluidos:
Crea las denominaciones de billetes y monedas peruanos: // Billetes
S / 200.00
S / 100.00
S / 50.00
S / 20.00
S / 10.00
// Monedas
S / 5.00
S / 2.00
S / 1.00
S / 0.50
S / 0.20
S / 0.10
Usado para:
Apertura de caja (desglose de billetes)
Cierre de caja (arqueo)
Reportes de efectivo
Crea datos de prueba para el reporte de utilidades:
Clientes de ejemplo
Productos de ejemplo
Ventas simuladas
Compras simuladas
Este seeder es opcional y solo para testing. No lo ejecutes en producción.
Ejecutar Seeder Específico
php artisan db:seed --class=DenominacionesBilletesSeeder
Crear Seeder Personalizado
php artisan make:seeder UsuarioAdminSeeder
database/seeders/UsuarioAdminSeeder.php
<? php
namespace Database\Seeders ;
use Illuminate\Database\ Seeder ;
use App\Models\ User ;
use Illuminate\Support\Facades\ Hash ;
class UsuarioAdminSeeder extends Seeder
{
public function run () : void
{
User :: create ([
'name' => 'Administrador' ,
'email' => '[email protected] ' ,
'password' => Hash :: make ( 'password' ),
'rol_id' => 1 , // Admin
'id_empresa' => 1 ,
'puede_abrir_caja' => true ,
'puede_cerrar_caja' => true ,
'puede_autorizar_cierre' => true ,
'puede_registrar_movimientos' => true ,
'puede_ver_reportes' => true ,
]);
}
}
Scoping Multi-empresa
Aunque el sistema tiene esquema multi-empresa, Santo Domingo está configurado como monempresa (id_empresa = 1).
Global Scope en Modelos
Todos los modelos principales tienen scoping automático:
protected static function booted ()
{
static :: addGlobalScope ( 'empresa' , function ( Builder $query ) {
if ( auth () -> check ()) {
$query -> where ( 'id_empresa' , auth () -> user () -> id_empresa );
}
});
}
Esto significa que automáticamente todas las consultas filtran por empresa del usuario autenticado.
Desactivar Scope Temporalmente
// Sin scope
$todasLasVentas = Venta :: withoutGlobalScope ( 'empresa' ) -> get ();
// Con scope (default)
$ventasDeEmpresa = Venta :: all (); // Solo id_empresa = 1
Mantenimiento de Base de Datos
Backup
Manual (mysqldump)
Automatizado (cron)
mysqldump -u factura_user -p factura_santoD > backup_ $( date +%Y%m%d ) .sql
Restaurar Backup
# Desde SQL
mysql -u factura_user -p factura_santoD < backup_20240306.sql
# Desde GZ
gunzip < backup_20240306.sql.gz | mysql -u factura_user -p factura_santoD
Optimizar Tablas
-- Optimizar todas las tablas
OPTIMIZE TABLE ventas, detalle_ventas, clientes, productos;
-- Analizar tablas
ANALYZE TABLE ventas, detalle_ventas;
-- Reparar tabla (si hay corrupción)
REPAIR TABLE ventas;
Limpiar Datos Antiguos
-- Eliminar ventas de prueba (beta)
DELETE FROM ventas WHERE estado_sunat = 'pendiente' AND created_at < DATE_SUB( NOW (), INTERVAL 90 DAY );
-- Archivar logs antiguos
DELETE FROM auditoria_cajas WHERE created_at < DATE_SUB( NOW (), INTERVAL 1 YEAR );
-- Limpiar sesiones expiradas
DELETE FROM sessions WHERE last_activity < UNIX_TIMESTAMP(DATE_SUB( NOW (), INTERVAL 30 DAY ));
Índices Importantes
Las migraciones ya incluyen índices optimizados:
-- Ventas
INDEX idx_empresa (id_empresa)
INDEX idx_cliente (id_cliente)
INDEX idx_fecha (fecha_emision)
INDEX idx_estado (estado_sunat)
UNIQUE idx_serie_numero (id_empresa, serie, numero)
-- Productos
INDEX idx_codigo (codigo)
INDEX idx_empresa (id_empresa)
-- Clientes
INDEX idx_documento (tipo_documento, numero_documento)
INDEX idx_empresa (id_empresa)
Query Optimization
// Mal - N+1 queries
$ventas = Venta :: all ();
foreach ( $ventas as $venta ) {
echo $venta -> cliente -> nombre ; // Query por cada venta
}
// Bien - Eager loading
$ventas = Venta :: with ( 'cliente' , 'detalles.producto' ) -> get ();
foreach ( $ventas as $venta ) {
echo $venta -> cliente -> nombre ; // Sin queries adicionales
}
Testing Database
Los tests usan SQLite in-memory (configurado en phpunit.xml):
< env name = "DB_CONNECTION" value = "sqlite" />
< env name = "DB_DATABASE" value = ":memory:" />
Ejecutar tests:
composer test
# o
php artisan test
Troubleshooting
Error 3780: Foreign key constraint
SQLSTATE[HY000]: General error: 3780 Referencing column
'id_empresa' and referenced column 'id_empresa' in foreign
key constraint are incompatible.
Solución : Usa $table->integer('id_empresa') (NO unsignedInteger) porque empresas.id_empresa es INT signed.
Si ves caracteres extraños (ñ, tildes): ALTER DATABASE factura_santoD CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- Por cada tabla
ALTER TABLE ventas CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
Migraciones fuera de orden
Si las migraciones fallan por dependencias: php artisan migrate:fresh # Drop all + re-run
migrate:fresh elimina todos los datos . Solo en desarrollo.
Verifica que MySQL esté corriendo: sudo systemctl status mysql
sudo systemctl start mysql
Próximos Pasos
Certificados SUNAT Configurar certificados digitales y credenciales
Configuración Configuración detallada del archivo .env
Arquitectura Comprender la arquitectura del sistema
Inicio Rápido Guía de instalación rápida