Visión General
Santo Domingo Facturación es un sistema híbrido que combina:
Backend : Laravel 12 (PHP 8.2+)
Frontend : React 19 con componentes montados desde Blade
Base de datos : MySQL 8.0+
Build tool : Vite 7
Integración : Greenter (SUNAT XML/SOAP/REST)
No es una SPA tradicional . Este sistema usa Blade templates que montan componentes React mediante el atributo data-react-component. La navegación entre páginas son cargas completas, no client-side routing.
Modelo de Renderizado
Patrón Blade + React
Cada página sigue este flujo:
Ruta Laravel
Define la ruta en routes/web.php: Route :: get ( '/ventas' , function () {
return view ( 'facturacion.ventas' );
}) -> middleware ([ 'auth' , 'check.permission:ventas.view' ]);
Vista Blade
La vista Blade monta el componente React: resources/views/facturacion/ventas.blade.php
@extends ( 'layouts.app' )
@section ( 'content' )
< div data-react-component = "VentasList" ></ div >
@endsection
Registro del componente
El componente debe estar registrado en resources/js/app.jsx: const components = {
Login ,
DashboardApp ,
VentasList , // ← Debe estar aquí
// ... otros componentes
};
Montaje automático
El script de bootstrap monta automáticamente todos los componentes con data-react-component: document . querySelectorAll ( '[data-react-component]' ). forEach ( el => {
const componentName = el . getAttribute ( 'data-react-component' );
const Component = components [ componentName ];
if ( Component ) {
const root = createRoot ( el );
root . render (
< QueryClientProvider client = { queryClient } >
< Component />
</ QueryClientProvider >
);
}
});
Navegación
La navegación NO usa React Router. Cada cambio de página es una carga completa:
// Navegación correcta
window . location . href = baseUrl ( '/ventas' );
// Links
< a href = { baseUrl ( '/productos' ) } > Productos </ a >
Importante : Usa siempre baseUrl() de @/lib/baseUrl para soportar instalaciones en subdirectorios (configurado via VITE_BASE_URL).
Arquitectura Backend
Estructura MVC
app/
├── Http/
│ ├── Controllers/
│ │ ├── Api/ # Controllers API JSON
│ │ ├── Exports/ # Exportación Excel
│ │ ├── Reportes/ # PDFs con mPDF
│ │ ├── VentasController.php
│ │ ├── GuiaRemisionController.php
│ │ └── ...
│ └── Middleware/
│ ├── CheckPermission.php # Permisos resource.action
│ └── TokenFromQuery.php # Auth via ?token= para PDFs
├── Models/
│ ├── Venta.php
│ ├── GuiaRemision.php
│ ├── Cliente.php
│ └── ...
├── Services/
│ ├── SunatService.php # 700+ líneas - Core SUNAT
│ ├── ProductoService.php # Lógica de stock
│ ├── CajaService.php
│ └── Contracts/ # Interfaces de servicios
└── ...
Capa de Servicios
SunatService (Core)
Servicio central para toda la integración SUNAT:
app/Services/SunatService.php
class SunatService
{
// Crea instancia Greenter SEE configurada
public function getSee ( Empresa $empresa , string $tipoDoc = 'facturacion' ) : See
// Genera y envía Factura/Boleta
public function enviarFactura ( Venta $venta ) : array
// Genera y envía Nota de Crédito
public function enviarNotaCredito ( Venta $venta ) : array
// Envía Guía de Remisión (API REST GRE)
public function enviarGuiaRemision ( GuiaRemision $guia ) : array
// Consulta ticket GRE (async)
public function consultarTicketGRE ( string $ticket , Empresa $empresa ) : array
// Genera Resumen Diario (boletas)
public function generarResumenDiario ( string $fecha , Empresa $empresa ) : array
// Comunicación de Baja (anulación)
public function generarComunicacionBaja ( array $documentos , Empresa $empresa ) : array
}
Flujos SUNAT por tipo de documento:
Facturas/Notas (SOAP)
Guías (REST GRE)
Resumen Diario (Boletas)
Protocolo : SOAP síncrono
Respuesta : CDR inmediato
Endpoint : e-factura.sunat.gob.pe
Protocolo : REST asíncrono
Auth : OAuth2 (client_id/secret)
Flujo : enviar() → ticket → consultarTicket() → CDR
Endpoint : api-cpe.sunat.gob.pe
Protocolo : SOAP asíncrono
Requiere : Queue worker activo
Plazo : Envío diario de boletas
ProductoService
Maneja movimientos de stock:
app/Services/ProductoService.php
class ProductoService
{
// Registra entrada de stock (compra, ajuste positivo)
public function registrarEntrada ( Producto $producto , float $cantidad , string $motivo )
// Registra salida (venta, ajuste negativo)
public function registrarSalida ( Producto $producto , float $cantidad , string $motivo )
// Calcula stock disponible
public function calcularStock ( Producto $producto ) : float
}
Todos los movimientos se registran en movimientos_stock con trazabilidad completa.
Sistema de Permisos
// Middleware CheckPermission
Route :: get ( '/ventas/create' , [ VentasController :: class , 'create' ])
-> middleware ( 'check.permission:ventas.create' );
Formato : {resource}.{action}
Ejemplos:
ventas.create
ventas.view
ventas.edit
ventas.delete
productos.view
cuentas-cobrar.edit
caja.open
El rol Admin (rol_id = 1) bypasea todas las verificaciones de permisos automáticamente.
Permisos de Caja (especiales)
Los permisos de caja son por usuario , no por rol:
// En la tabla users
$user -> puede_abrir_caja
$user -> puede_cerrar_caja
$user -> puede_autorizar_cierre
$user -> puede_registrar_movimientos
$user -> puede_ver_reportes
Arquitectura Frontend
Estructura de Componentes
resources/js/
├── components/
│ ├── ui/ # Componentes primitivos (Radix UI)
│ │ ├── button.jsx
│ │ ├── modal.jsx
│ │ ├── select.jsx
│ │ ├── input.jsx
│ │ └── data-table.jsx
│ ├── shared/ # Componentes compartidos
│ │ ├── ClienteAutocomplete.jsx
│ │ ├── ProductoAutocomplete.jsx
│ │ ├── PaymentSchedule.jsx
│ │ └── MetodoPago.jsx
│ ├── Facturacion/
│ │ └── Ventas/
│ │ ├── VentasList.jsx # Página principal
│ │ ├── VentaForm.jsx # Formulario
│ │ ├── columns/ # Definiciones columnas
│ │ ├── hooks/ # useVentas, useMutations
│ │ └── components/ # Modales, detalles
│ ├── GuiaRemision/
│ ├── NotaCredito/
│ ├── Finanzas/
│ └── ...
├── services/ # API clients
│ ├── api.js # Axios instance base
│ ├── ventasService.js
│ └── productosService.js
├── stores/ # Zustand stores
│ └── authStore.js
├── lib/
│ └── baseUrl.js # Helper para subdirectorios
└── app.jsx # Entry point + registro componentes
Patrón de Páginas de Lista
Todas las páginas de listado siguen este patrón:
// VentasList.jsx
import { useVentas } from './hooks/useVentas' ;
import { DataTable } from '@/components/ui/data-table' ;
import { columns } from './columns/ventasColumns' ;
export default function VentasList () {
const { data : ventas , isLoading } = useVentas ();
return (
< DataTable
columns = { columns }
data = { ventas }
searchable
searchPlaceholder = "Buscar ventas..."
/>
);
}
Componentes clave:
page.jsx : Componente principal de la página
columns/ : Definiciones de columnas con acciones
hooks/ : Custom hooks para data fetching (React Query)
components/ : Modales, detalles, formularios anidados
Data Fetching con React Query
// hooks/useVentas.js
import { useQuery } from '@tanstack/react-query' ;
import { fetchVentas } from '@/services/ventasService' ;
export function useVentas () {
return useQuery ({
queryKey: [ 'ventas' ],
queryFn: fetchVentas ,
staleTime: 1000 * 60 * 5 , // 5 minutos
});
}
Configuración global:
const queryClient = new QueryClient ({
defaultOptions: {
queries: {
staleTime: 1000 * 60 * 5 , // 5 minutos
gcTime: 1000 * 60 * 10 , // 10 minutos
},
},
});
Comunicación con API
// services/api.js
import axios from 'axios' ;
import { baseUrl } from '@/lib/baseUrl' ;
const api = axios . create ({
baseURL: baseUrl ( '/api' ),
headers: {
'Content-Type' : 'application/json' ,
'Accept' : 'application/json' ,
},
});
// Interceptor para añadir token
api . interceptors . request . use ( config => {
const token = localStorage . getItem ( 'auth_token' );
if ( token ) {
config . headers . Authorization = `Bearer ${ token } ` ;
}
return config ;
});
UI Library Stack
Radix UI Componentes primitivos headless (Dialog, Select, Dropdown)
Tailwind CSS 4 Utility-first CSS con colores custom (#c7161d, #ece6a3)
TanStack Table DataTable con sorting, filtering, pagination
Recharts Gráficos para dashboard (ventas, utilidades)
Lucide React Sistema de iconos
SweetAlert2 Diálogos de confirmación
Quill Editor rich text (notas, observaciones)
jsPDF + html2canvas Generación PDF client-side
Base de Datos
Esquema Multi-empresa
Aunque el sistema tiene soporte multi-empresa en el esquema, Santo Domingo está configurado para empresa única (id_empresa = 1).
-- Todas las tablas principales tienen:
id_empresa INT NOT NULL
-- Scoping automático en modelos:
protected static function booted()
{
static ::addGlobalScope( 'empresa' , function (Builder $query) {
if (auth() -> check ()) {
$query -> where ( 'id_empresa' , auth() -> user() -> id_empresa);
}
});
}
Tablas Principales
Facturación
Comercial
Almacén
Finanzas
Seguridad
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
clientes : Clientes (DNI/RUC)
proveedores : Proveedores
cotizaciones : Cotizaciones
detalle_cotizaciones : Items de cotizaciones
transportistas : Datos de transportistas
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)
cajas : Cajas registradoras
movimientos_caja : Ingresos/egresos
arqueos_diarios : Arqueos de caja
bancos : Catálogo de bancos
cuentas_bancarias : Cuentas de empresa
movimientos_bancarios : Transacciones bancarias
metodos_pago : Métodos de pago disponibles
caja_metodos_pago : Relación caja-métodos
users : Usuarios del sistema
roles : Roles de usuario
permisos : Catálogo de permisos
rol_permisos : Relación rol-permisos
empresas : Datos de empresa (RUC, SOL)
Constraint Importante
La columna empresas.id_empresa es INT (signed). Cualquier FK a id_empresa debe usar $table->integer('id_empresa') — NO unsignedInteger, o fallará con error 3780.
File Storage
Estructura SUNAT
storage/app/sunat/
├── xml/
│ └── {ruc}/
│ └── {ruc}-{tipo}-{serie}-{numero}.xml
├── cdr/
│ └── {ruc}/
│ └── R-{ruc}-{tipo}-{serie}-{numero}.zip
└── certificados/
├── cert.pem # Certificado prueba global
└── {ruc}-cert.pem # Certificado específico empresa
Nomenclatura de archivos:
Factura: 20612706702-01-F001-00000123.xml
Boleta: 20612706702-03-B001-00000045.xml
Guía: 20612706702-09-T001-00000001.xml
CDR: R-20612706702-01-F001-00000123.zip
Configuración
Archivo config/sunat.php
return [
'igv' => env ( 'SUNAT_IGV' , 0.18 ), // 18% IGV Perú
'endpoints' => [
'facturacion' => [
'beta' => 'https://e-beta.sunat.gob.pe/ol-ti-itcpfegem-beta/billService' ,
'production' => 'https://e-factura.sunat.gob.pe/ol-ti-itcpfegem/billService' ,
],
'guia' => [
'beta' => 'https://e-beta.sunat.gob.pe/ol-ti-itemision-guia-gem-beta/billService' ,
'production' => 'https://e-guiaremision.sunat.gob.pe/ol-ti-itemision-guia-gem/billService' ,
],
'gre' => [
'auth' => 'https://api-seguridad-test.sunat.gob.pe/v1' ,
'cpe' => 'https://api-cpe-test.sunat.gob.pe/v1' ,
'client_id' => env ( 'SUNAT_GRE_CLIENT_ID' ),
'client_secret' => env ( 'SUNAT_GRE_CLIENT_SECRET' ),
],
],
'beta' => [
'ruc' => '20000000001' ,
'usuario_sol' => 'MODDATOS' ,
'clave_sol' => 'moddatos' ,
],
];
Flujo Completo: Factura
Próximos Pasos
Base de Datos Explora el esquema de base de datos completo
Configuración Configuración detallada de la aplicación
Certificados SUNAT Setup de certificados y credenciales
API Reference Documentación de endpoints API