Skip to main content

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:
1

Ruta Laravel

Define la ruta en routes/web.php:
Route::get('/ventas', function () {
    return view('facturacion.ventas');
})->middleware(['auth', 'check.permission:ventas.view']);
2

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
3

Registro del componente

El componente debe estar registrado en resources/js/app.jsx:
const components = {
    Login,
    DashboardApp,
    VentasList,  // ← Debe estar aquí
    // ... otros componentes
};
4

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>
        );
    }
});
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:
  • Protocolo: SOAP síncrono
  • Respuesta: CDR inmediato
  • Endpoint: e-factura.sunat.gob.pe

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:
  1. page.jsx: Componente principal de la página
  2. columns/: Definiciones de columnas con acciones
  3. hooks/: Custom hooks para data fetching (React Query)
  4. 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:
app.jsx
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

  • 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

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

Build docs developers (and LLMs) love