Update: nueva version Horux Despachos
This commit is contained in:
534
docs/architecture/api-reference.md
Normal file
534
docs/architecture/api-reference.md
Normal file
@@ -0,0 +1,534 @@
|
||||
# API Reference - Horux360
|
||||
|
||||
**Base URL:** `https://horuxfin.com/api`
|
||||
**Última actualización:** 2026-04-11
|
||||
|
||||
---
|
||||
|
||||
## Autenticación
|
||||
|
||||
Todos los endpoints (excepto auth y webhooks) requieren header:
|
||||
```
|
||||
Authorization: Bearer <accessToken>
|
||||
```
|
||||
|
||||
### Rate Limits (por IP)
|
||||
| Endpoint | Límite | Ventana |
|
||||
|----------|--------|---------|
|
||||
| `POST /auth/login` | 10 requests | 15 minutos |
|
||||
| `POST /auth/register` | 3 requests | 1 hora |
|
||||
| `POST /auth/refresh` | 20 requests | 15 minutos |
|
||||
| General `/api/*` | 30 requests/s | burst 50 |
|
||||
|
||||
---
|
||||
|
||||
## Auth (`/api/auth`)
|
||||
|
||||
### `POST /auth/register`
|
||||
Registra nueva empresa y usuario admin. Provisiona base de datos dedicada.
|
||||
|
||||
**Body:**
|
||||
```json
|
||||
{
|
||||
"empresa": { "nombre": "Mi Empresa", "rfc": "ABC123456789" },
|
||||
"usuario": { "nombre": "Juan", "email": "juan@empresa.com", "password": "min8chars" }
|
||||
}
|
||||
```
|
||||
|
||||
**Response:** `{ accessToken, refreshToken, user: UserInfo }`
|
||||
|
||||
### `POST /auth/login`
|
||||
```json
|
||||
{ "email": "usuario@empresa.com", "password": "..." }
|
||||
```
|
||||
**Response:** `{ accessToken, refreshToken, user: UserInfo }`
|
||||
|
||||
### `POST /auth/refresh`
|
||||
```json
|
||||
{ "refreshToken": "..." }
|
||||
```
|
||||
**Response:** `{ accessToken, refreshToken }`
|
||||
|
||||
### `POST /auth/logout` *(requiere auth)*
|
||||
```json
|
||||
{ "refreshToken": "..." }
|
||||
```
|
||||
|
||||
### `GET /auth/me` *(requiere auth)*
|
||||
**Response:** `UserInfo`
|
||||
|
||||
---
|
||||
|
||||
## Dashboard (`/api/dashboard`)
|
||||
|
||||
### `GET /dashboard/kpis`
|
||||
KPIs principales: ingresos, egresos, utilidad, margen, IVA balance, IVA a favor acumulado/histórico, conteo de CFDIs por régimen.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin` (default: mes actual)
|
||||
|
||||
### `GET /dashboard/ingresos-egresos`
|
||||
Datos mensuales de ingresos/egresos desglosados por régimen fiscal para gráfica anual.
|
||||
|
||||
**Query:** `año` (default: año actual)
|
||||
|
||||
### `GET /dashboard/regimenes-periodo`
|
||||
Regímenes fiscales presentes en los CFDIs del rango de fechas.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /dashboard/alertas`
|
||||
Alertas activas no resueltas, ordenadas por prioridad.
|
||||
|
||||
**Query:** `limit` (default: 5)
|
||||
|
||||
---
|
||||
|
||||
## CFDI (`/api/cfdi`)
|
||||
|
||||
### `GET /cfdi`
|
||||
Lista paginada de CFDIs con filtros.
|
||||
|
||||
**Query:**
|
||||
- `page` (default: 1), `limit` (default: 20)
|
||||
- `tipo`: `EMITIDO` | `RECIBIDO`
|
||||
- `tipoComprobante`: `I` | `E` | `T` | `P` | `N`
|
||||
- `estado`: `Vigente` | `Cancelado` | `0` | `1`
|
||||
- `fechaInicio`, `fechaFin`
|
||||
- `rfc`, `emisor`, `receptor`, `search`
|
||||
|
||||
### `GET /cfdi/resumen`
|
||||
Resumen de conteo por tipo y estado.
|
||||
|
||||
### `GET /cfdi/emisores`
|
||||
Lista de emisores únicos (RFC + nombre). **Query:** `search`
|
||||
|
||||
### `GET /cfdi/receptores`
|
||||
Lista de receptores únicos (RFC + nombre). **Query:** `search`
|
||||
|
||||
### `GET /cfdi/drill-down`
|
||||
CFDIs filtrados para drill-down desde dashboard.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`, `type`, `tipoComprobante`, `page`, `limit`
|
||||
|
||||
### `GET /cfdi/:id`
|
||||
Detalle de un CFDI.
|
||||
|
||||
### `GET /cfdi/:id/conceptos`
|
||||
Conceptos (líneas de detalle) de un CFDI.
|
||||
|
||||
### `GET /cfdi/:id/xml`
|
||||
XML original del CFDI (descarga como archivo).
|
||||
|
||||
### `POST /cfdi`
|
||||
Crear un CFDI individual. Sujeto a límite de plan.
|
||||
|
||||
### `POST /cfdi/bulk`
|
||||
Carga masiva de CFDIs. Body limit: 50MB. Sujeto a límite de plan.
|
||||
|
||||
### `DELETE /cfdi/:id`
|
||||
Eliminar un CFDI.
|
||||
|
||||
---
|
||||
|
||||
## Impuestos (`/api/impuestos`)
|
||||
|
||||
### `GET /impuestos/iva/mensual`
|
||||
Datos mensuales de IVA (trasladado, acreditable, retenido, resultado, acumulado).
|
||||
|
||||
**Query:** `año` (default: año actual)
|
||||
|
||||
### `GET /impuestos/iva/resumen`
|
||||
Resumen de IVA para un rango de fechas con desglose por régimen.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /impuestos/isr/resumen`
|
||||
Resumen de ISR para un rango de fechas con desglose por régimen.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /impuestos/isr/coeficiente`
|
||||
Coeficiente de utilidad del tenant para un año.
|
||||
|
||||
**Query:** `anio`
|
||||
|
||||
### `PUT /impuestos/isr/coeficiente` *(admin)*
|
||||
Establecer coeficiente de utilidad.
|
||||
|
||||
**Body:** `{ anio, coeficiente }`
|
||||
|
||||
---
|
||||
|
||||
## Alertas (`/api/alertas`)
|
||||
|
||||
### `GET /alertas`
|
||||
Lista de alertas con filtros opcionales.
|
||||
|
||||
**Query:** `leida`, `resuelta`, `prioridad`
|
||||
|
||||
### `GET /alertas/:id`
|
||||
Detalle de una alerta.
|
||||
|
||||
### `GET /alertas/automaticas`
|
||||
Alertas auto-generadas de cumplimiento fiscal (lista negra, concentración, discrepancias, cancelaciones, efectivo).
|
||||
|
||||
### `GET /alertas/manuales`
|
||||
Obligaciones manuales pendientes.
|
||||
|
||||
### `GET /alertas/stats`
|
||||
Estadísticas de alertas (total, no leídas, por prioridad).
|
||||
|
||||
### Drill-down de alertas
|
||||
|
||||
| Endpoint | Descripción |
|
||||
|----------|-------------|
|
||||
| `GET /alertas/drilldown/lista-negra-clientes` | Clientes en lista negra SAT |
|
||||
| `GET /alertas/drilldown/lista-negra-proveedores` | Proveedores en lista negra SAT |
|
||||
| `GET /alertas/drilldown/concentracion-clientes` | Análisis de concentración (clientes) |
|
||||
| `GET /alertas/drilldown/concentracion-proveedores` | Análisis de concentración (proveedores) |
|
||||
| `GET /alertas/drilldown/discrepancia-regimen` | Discrepancias de régimen fiscal |
|
||||
| `GET /alertas/drilldown/cancelaciones` | CFDIs cancelados |
|
||||
| `GET /alertas/drilldown/efectivo` | Análisis de método de pago efectivo |
|
||||
|
||||
### `POST /alertas`
|
||||
Crear alerta manual.
|
||||
|
||||
### `PATCH /alertas/:id`
|
||||
Actualizar alerta (leída, resuelta).
|
||||
|
||||
### `PATCH /alertas/manuales/:id/resolver`
|
||||
Resolver obligación manual.
|
||||
|
||||
### `DELETE /alertas/:id`
|
||||
Eliminar alerta.
|
||||
|
||||
### `POST /alertas/mark-all-read`
|
||||
Marcar todas las alertas como leídas.
|
||||
|
||||
---
|
||||
|
||||
## Conciliacion (`/api/conciliacion`) *(requiere feature 'conciliacion')*
|
||||
|
||||
### `GET /conciliacion`
|
||||
Lista CFDIs con estado de conciliacion (pendiente o conciliado).
|
||||
|
||||
**Query:**
|
||||
- `tipo`: `EMITIDO` | `RECIBIDO` (requerido)
|
||||
- `fechaInicio`, `fechaFin`: rango de fecha de emision
|
||||
- `regimen`: clave de regimen fiscal (opcional)
|
||||
- `estado`: `conciliado` | `pendiente` (opcional, default: todos)
|
||||
|
||||
**Reglas de exclusion:**
|
||||
- Recibidos: excluye PPD
|
||||
- Emitidos: excluye PPD para todos los regimenes excepto 605 y 616
|
||||
- Tipo P usa `monto_pago_mxn` en vez de `total_mxn`
|
||||
|
||||
### `POST /conciliacion` *(admin, contador)*
|
||||
Conciliar CFDIs en batch. Auto-concilia PPD si la factura P lleva saldo a 0.
|
||||
```json
|
||||
{ "cfdiIds": [1, 2, 3], "fechaDePago": "2026-04-10", "idBanco": 1 }
|
||||
```
|
||||
|
||||
### `DELETE /conciliacion/:id` *(admin, contador)*
|
||||
Desconciliar un CFDI. Si es tipo P, tambien desconcilia la PPD auto-conciliada.
|
||||
|
||||
---
|
||||
|
||||
## Bancos (`/api/bancos`)
|
||||
|
||||
### `GET /bancos`
|
||||
Listar bancos del tenant.
|
||||
|
||||
### `POST /bancos` *(admin)*
|
||||
Crear banco.
|
||||
```json
|
||||
{ "banco": "BBVA", "terminacionCuenta": "1234" }
|
||||
```
|
||||
|
||||
### `PUT /bancos/:id` *(admin)*
|
||||
Editar banco.
|
||||
|
||||
### `DELETE /bancos/:id` *(admin)*
|
||||
Eliminar banco. Falla si tiene conciliaciones asociadas.
|
||||
|
||||
---
|
||||
|
||||
## Calendario (`/api/calendario`)
|
||||
|
||||
### `GET /calendario/generados`
|
||||
Eventos fiscales generados para un año, basados en el catálogo de eventos fiscales y días inhábiles.
|
||||
|
||||
**Query:** `año` (default: año actual)
|
||||
|
||||
---
|
||||
|
||||
## Reportes (`/api/reportes`) *(requiere feature 'reportes' en plan)*
|
||||
|
||||
### `GET /reportes/estado-resultados`
|
||||
Estado de resultados (P&L).
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /reportes/flujo-efectivo`
|
||||
Flujo de efectivo.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /reportes/comparativo`
|
||||
Comparativo año contra año.
|
||||
|
||||
**Query:** `año`
|
||||
|
||||
### `GET /reportes/cuentas-x-pagar`
|
||||
Cuentas por pagar (facturas con saldo pendiente).
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`, `regimen`
|
||||
|
||||
### `GET /reportes/cuentas-x-cobrar`
|
||||
Cuentas por cobrar.
|
||||
|
||||
**Query:** `fechaInicio`, `fechaFin`, `regimen`
|
||||
|
||||
### `GET /reportes/concentrado-rfc`
|
||||
Concentrado por RFC (top clientes/proveedores).
|
||||
|
||||
**Query:** `tipo` (`cliente` | `proveedor`), `fechaInicio`, `fechaFin`
|
||||
|
||||
---
|
||||
|
||||
## Export (`/api/export`)
|
||||
|
||||
### `GET /export/cfdis`
|
||||
Exporta CFDIs a Excel.
|
||||
|
||||
**Query:** `tipo`, `estado`, `fechaInicio`, `fechaFin`
|
||||
|
||||
### `GET /export/reporte`
|
||||
Exporta reporte a Excel.
|
||||
|
||||
**Query:** `tipo` (`estado-resultados` | `flujo-efectivo`), `fechaInicio`, `fechaFin`
|
||||
|
||||
---
|
||||
|
||||
## Regímenes (`/api/regimenes`)
|
||||
|
||||
### `GET /regimenes`
|
||||
Catálogo completo de regímenes fiscales SAT.
|
||||
|
||||
### `GET /regimenes/activos`
|
||||
Regímenes activos del tenant.
|
||||
|
||||
### `PUT /regimenes/activos` *(admin)*
|
||||
Configurar regímenes activos.
|
||||
|
||||
**Body:** `{ regimenIds: number[] }`
|
||||
|
||||
### `GET /regimenes/ignorados`
|
||||
Regímenes ignorados del tenant (excluidos de cálculos).
|
||||
|
||||
### `PUT /regimenes/ignorados` *(admin)*
|
||||
Configurar regímenes ignorados.
|
||||
|
||||
**Body:** `{ regimenIds: number[] }`
|
||||
|
||||
---
|
||||
|
||||
## FIEL (`/api/fiel`)
|
||||
|
||||
### `POST /fiel/upload`
|
||||
Subir y validar credenciales FIEL (e.firma).
|
||||
```json
|
||||
{
|
||||
"cerFile": "<base64>",
|
||||
"keyFile": "<base64>",
|
||||
"password": "..."
|
||||
}
|
||||
```
|
||||
- Archivos max 50KB cada uno
|
||||
- Password max 256 caracteres
|
||||
- Encriptación AES-256-GCM por componente
|
||||
|
||||
### `GET /fiel/status`
|
||||
Estado actual de la FIEL: configurada, RFC, serial, validez, días hasta expiración.
|
||||
|
||||
### `DELETE /fiel`
|
||||
Eliminar credenciales FIEL.
|
||||
|
||||
---
|
||||
|
||||
## SAT Sync (`/api/sat`)
|
||||
|
||||
### `POST /sat/sync`
|
||||
Iniciar sincronización manual con el SAT.
|
||||
```json
|
||||
{ "type": "daily", "dateFrom": "2026-01-01", "dateTo": "2026-01-31" }
|
||||
```
|
||||
|
||||
### `GET /sat/sync/status`
|
||||
Estado actual de sincronización (job activo, último completado, total sincronizados).
|
||||
|
||||
### `GET /sat/sync/history`
|
||||
Historial de sincronizaciones paginado.
|
||||
|
||||
**Query:** `page`, `limit`
|
||||
|
||||
### `GET /sat/sync/:id`
|
||||
Detalle de un job de sincronización (progreso, paquetes, errores).
|
||||
|
||||
### `POST /sat/sync/:id/retry`
|
||||
Reintentar un job fallido.
|
||||
|
||||
### `GET /sat/cron` *(admin global)*
|
||||
Info del cron de sincronización automática (03:00 AM diario).
|
||||
|
||||
### `POST /sat/cron/run` *(admin global)*
|
||||
Ejecutar sincronización global manualmente.
|
||||
|
||||
---
|
||||
|
||||
## Usuarios (`/api/usuarios`)
|
||||
|
||||
### `GET /usuarios`
|
||||
Usuarios del tenant actual.
|
||||
|
||||
### `GET /usuarios/global/all` *(admin global)*
|
||||
Todos los usuarios de todas las empresas.
|
||||
|
||||
### `POST /usuarios/invite`
|
||||
Invitar usuario (genera password temporal con `crypto.randomBytes`).
|
||||
```json
|
||||
{ "email": "nuevo@empresa.com", "nombre": "María", "role": "contador" }
|
||||
```
|
||||
|
||||
### `PATCH /usuarios/:id`
|
||||
Actualizar usuario (nombre, role, active). Scope: tenant.
|
||||
|
||||
### `DELETE /usuarios/:id`
|
||||
Eliminar usuario. Scope: tenant.
|
||||
|
||||
### `PATCH /usuarios/global/:id` *(admin global)*
|
||||
Actualizar usuario de cualquier empresa. Puede cambiar tenant.
|
||||
|
||||
### `DELETE /usuarios/global/:id` *(admin global)*
|
||||
Eliminar usuario de cualquier empresa.
|
||||
|
||||
---
|
||||
|
||||
## Tenants / Clientes (`/api/tenants`) *(admin global)*
|
||||
|
||||
### `GET /tenants`
|
||||
Lista de todos los tenants/clientes.
|
||||
|
||||
### `GET /tenants/:id`
|
||||
Detalle de un tenant.
|
||||
|
||||
### `POST /tenants`
|
||||
Crear nuevo tenant. Provisiona base de datos dedicada. Envía email al admin.
|
||||
```json
|
||||
{
|
||||
"nombre": "Empresa Nueva",
|
||||
"rfc": "ENE123456789",
|
||||
"plan": "business",
|
||||
"cfdiLimit": 500,
|
||||
"usersLimit": 3,
|
||||
"adminNombre": "Pedro",
|
||||
"adminEmail": "pedro@nueva.com",
|
||||
"amount": 999
|
||||
}
|
||||
```
|
||||
|
||||
### `PUT /tenants/:id`
|
||||
Actualizar tenant (nombre, rfc, plan, limits, active).
|
||||
|
||||
### `DELETE /tenants/:id`
|
||||
Soft delete — renombra la base de datos a `{name}_deleted_{timestamp}`.
|
||||
|
||||
---
|
||||
|
||||
## Suscripciones (`/api/subscriptions`) *(admin global)*
|
||||
|
||||
### `GET /subscriptions/:tenantId`
|
||||
Suscripción activa del tenant.
|
||||
|
||||
### `POST /subscriptions/:tenantId/generate-link`
|
||||
Generar link de pago MercadoPago.
|
||||
|
||||
### `POST /subscriptions/:tenantId/mark-paid`
|
||||
Marcar como pagado manualmente.
|
||||
```json
|
||||
{ "amount": 999 }
|
||||
```
|
||||
|
||||
### `GET /subscriptions/:tenantId/payments`
|
||||
Historial de pagos.
|
||||
|
||||
---
|
||||
|
||||
## Webhooks (`/api/webhooks`)
|
||||
|
||||
### `POST /webhooks/mercadopago`
|
||||
Webhook de MercadoPago. Requiere headers:
|
||||
- `x-signature`: Firma HMAC-SHA256
|
||||
- `x-request-id`: ID del request
|
||||
|
||||
Maneja notificaciones de tipo `payment` y `preapproval`.
|
||||
|
||||
---
|
||||
|
||||
## Roles y Permisos
|
||||
|
||||
| Rol | Descripción | Acceso |
|
||||
|-----|-------------|--------|
|
||||
| `admin` | Administrador del tenant | Todo dentro de su tenant + invitar usuarios + configuración |
|
||||
| `contador` | Contador | CFDI, impuestos, reportes, dashboard, alertas |
|
||||
| `visor` | Solo lectura | Dashboard, CFDI (solo ver), reportes |
|
||||
|
||||
### Admin Global
|
||||
El admin del tenant con RFC `HTS240708LJA` tiene acceso adicional:
|
||||
- Gestión de todos los tenants (`/api/tenants`)
|
||||
- Suscripciones (`/api/subscriptions`)
|
||||
- SAT cron (`/api/sat/cron`)
|
||||
- Impersonación via `X-View-Tenant` header (bypass de plan limits)
|
||||
- Gestión global de usuarios (`/api/usuarios/global/*`)
|
||||
|
||||
---
|
||||
|
||||
## Middlewares
|
||||
|
||||
| Middleware | Descripción |
|
||||
|------------|-------------|
|
||||
| **auth** | Verifica JWT, extrae payload a `req.user`. Factory `authorize(...roles)` para roles. |
|
||||
| **tenant** | Resuelve pool de BD del tenant. Cache 5min. Soporta `X-View-Tenant` para admin global. |
|
||||
| **plan-limits** | Verifica suscripción activa. Read-only si inactiva. Limita creación de CFDIs. Cache 5min. |
|
||||
| **feature-gate** | Gate por features del plan (`requireFeature('reportes')`). 403 si no incluido. |
|
||||
| **error** | Handler centralizado. `AppError` para respuestas estructuradas; 500 para no manejados. |
|
||||
|
||||
---
|
||||
|
||||
## Tipos Compartidos (`@horux/shared`)
|
||||
|
||||
### UserInfo
|
||||
```typescript
|
||||
interface UserInfo {
|
||||
id: string;
|
||||
email: string;
|
||||
nombre: string;
|
||||
role: 'admin' | 'contador' | 'visor';
|
||||
tenantId: string;
|
||||
tenantName: string;
|
||||
tenantRfc: string;
|
||||
plan: string;
|
||||
}
|
||||
```
|
||||
|
||||
### JWTPayload
|
||||
```typescript
|
||||
interface JWTPayload {
|
||||
userId: string;
|
||||
email: string;
|
||||
role: Role;
|
||||
tenantId: string;
|
||||
iat?: number;
|
||||
exp?: number;
|
||||
}
|
||||
```
|
||||
397
docs/architecture/database-schema.prisma
Normal file
397
docs/architecture/database-schema.prisma
Normal file
@@ -0,0 +1,397 @@
|
||||
// Horux360 - Database Schema Documentation
|
||||
// PostgreSQL Multi-tenant: database-per-tenant
|
||||
// Última actualización: 2026-04-11
|
||||
//
|
||||
// ARQUITECTURA:
|
||||
// horux360 (central) ← Prisma: tenants, users, subscriptions, catálogos
|
||||
// horux_<rfc> (por tenant) ← Raw SQL (pg Pool): cfdis, cfdi_conceptos, rfcs, alertas
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BASE DE DATOS CENTRAL (horux360)
|
||||
// Gestionada por Prisma Client
|
||||
// ============================================
|
||||
|
||||
model Tenant {
|
||||
id String @id @default(uuid())
|
||||
nombre String
|
||||
rfc String @unique
|
||||
plan Plan @default(starter)
|
||||
databaseName String @unique @map("database_name")
|
||||
cfdiLimit Int @default(100) @map("cfdi_limit")
|
||||
usersLimit Int @default(1) @map("users_limit")
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
expiresAt DateTime? @map("expires_at")
|
||||
|
||||
users User[]
|
||||
fielCredential FielCredential?
|
||||
satSyncJobs SatSyncJob[]
|
||||
subscriptions Subscription[]
|
||||
payments Payment[]
|
||||
regimenesIgnorados TenantRegimenIgnorado[]
|
||||
regimenesActivos TenantRegimenActivo[]
|
||||
coeficientes CoeficienteUtilidad[]
|
||||
|
||||
@@map("tenants")
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
email String @unique
|
||||
passwordHash String @map("password_hash")
|
||||
nombre String
|
||||
role Role @default(visor)
|
||||
active Boolean @default(true)
|
||||
lastLogin DateTime? @map("last_login")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
|
||||
@@map("users")
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
id String @id @default(uuid())
|
||||
userId String @map("user_id")
|
||||
token String @unique
|
||||
expiresAt DateTime @map("expires_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("refresh_tokens")
|
||||
}
|
||||
|
||||
enum Plan {
|
||||
starter
|
||||
business
|
||||
professional
|
||||
enterprise
|
||||
}
|
||||
|
||||
enum Role {
|
||||
admin
|
||||
contador
|
||||
visor
|
||||
}
|
||||
|
||||
// ─── Catálogo de Regímenes Fiscales SAT ───
|
||||
|
||||
model Regimen {
|
||||
id Int @id @default(autoincrement())
|
||||
clave String @unique @db.VarChar(3) // Clave SAT: 601, 603, 605, 606, etc.
|
||||
descripcion String
|
||||
tipoPersona String @map("tipo_persona") @db.VarChar(20) // fisica, moral, ambos
|
||||
activo Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenantIgnorados TenantRegimenIgnorado[]
|
||||
tenantActivos TenantRegimenActivo[]
|
||||
|
||||
@@map("regimenes")
|
||||
}
|
||||
|
||||
model TenantRegimenIgnorado {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
regimenId Int @map("regimen_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, regimenId])
|
||||
@@map("tenant_regimenes_ignorados")
|
||||
}
|
||||
|
||||
model TenantRegimenActivo {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
regimenId Int @map("regimen_id")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
regimen Regimen @relation(fields: [regimenId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, regimenId])
|
||||
@@map("tenant_regimenes_activos")
|
||||
}
|
||||
|
||||
// ─── Catálogo de Eventos Fiscales ───
|
||||
|
||||
model EventoFiscalCatalogo {
|
||||
id Int @id @default(autoincrement())
|
||||
titulo String
|
||||
descripcion String?
|
||||
tipo String @db.VarChar(20) // declaracion, pago, obligacion, informativa
|
||||
diaBase Int @map("dia_base")
|
||||
mesRelativo Int @default(1) @map("mes_relativo")
|
||||
mesFijo Int? @map("mes_fijo")
|
||||
recurrencia String @default("mensual") @db.VarChar(20) // mensual, anual
|
||||
usaExtensionRfc Boolean @default(false) @map("usa_extension_rfc")
|
||||
regimenes String @default("todos") // 'todos' o CSV: '601,603,612'
|
||||
condicion String? @db.VarChar(50) // null, 'tiene_nomina', 'ingresos_4m'
|
||||
activo Boolean @default(true)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@map("eventos_fiscales_catalogo")
|
||||
}
|
||||
|
||||
/// Lista negra SAT (Art. 69-B CFF)
|
||||
model ListaNegra {
|
||||
id Int @id @default(autoincrement())
|
||||
rfc String @unique @db.VarChar(13)
|
||||
nombre String
|
||||
situacion String @db.VarChar(30) // Definitivo, Presunto, Desvirtuado, Sentencia Favorable
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
@@index([rfc])
|
||||
@@map("lista_negra")
|
||||
}
|
||||
|
||||
/// Días inhábiles fiscales (festivos oficiales de México)
|
||||
model DiaInhabil {
|
||||
id Int @id @default(autoincrement())
|
||||
fecha DateTime @unique @db.Date
|
||||
nombre String
|
||||
|
||||
@@map("dias_inhabiles")
|
||||
}
|
||||
|
||||
// ─── ISR ───
|
||||
|
||||
/// Tasas RESICO (Art. 113-E)
|
||||
model IsrResicoTasa {
|
||||
id Int @id @default(autoincrement())
|
||||
anio Int @map("anio")
|
||||
montoMaximo Decimal @map("monto_maximo") @db.Decimal(18, 2)
|
||||
porcentaje Decimal @db.Decimal(5, 2)
|
||||
|
||||
@@unique([anio, montoMaximo])
|
||||
@@map("isr_resico_tasas")
|
||||
}
|
||||
|
||||
/// Tarifa ISR progresiva (Art. 96) - mensual
|
||||
model IsrTarifa {
|
||||
id Int @id @default(autoincrement())
|
||||
anio Int @map("anio")
|
||||
limiteInferior Decimal @map("limite_inferior") @db.Decimal(18, 2)
|
||||
limiteSuperior Decimal? @map("limite_superior") @db.Decimal(18, 2)
|
||||
cuotaFija Decimal @map("cuota_fija") @db.Decimal(18, 2)
|
||||
porcentajeExcedente Decimal @map("porcentaje_excedente") @db.Decimal(5, 2)
|
||||
|
||||
@@unique([anio, limiteInferior])
|
||||
@@map("isr_tarifas")
|
||||
}
|
||||
|
||||
/// Coeficiente de utilidad por tenant/año
|
||||
model CoeficienteUtilidad {
|
||||
id Int @id @default(autoincrement())
|
||||
tenantId String @map("tenant_id")
|
||||
anio Int @map("anio")
|
||||
coeficiente Decimal @db.Decimal(10, 4)
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([tenantId, anio])
|
||||
@@map("coeficiente_utilidad")
|
||||
}
|
||||
|
||||
// ─── SAT Sync ───
|
||||
|
||||
model FielCredential {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @unique @map("tenant_id")
|
||||
rfc String @db.VarChar(13)
|
||||
cerData Bytes @map("cer_data")
|
||||
keyData Bytes @map("key_data")
|
||||
keyPasswordEncrypted Bytes @map("key_password_encrypted")
|
||||
cerIv Bytes @map("cer_iv")
|
||||
cerTag Bytes @map("cer_tag")
|
||||
keyIv Bytes @map("key_iv")
|
||||
keyTag Bytes @map("key_tag")
|
||||
passwordIv Bytes @map("password_iv")
|
||||
passwordTag Bytes @map("password_tag")
|
||||
serialNumber String? @map("serial_number") @db.VarChar(50)
|
||||
validFrom DateTime @map("valid_from")
|
||||
validUntil DateTime @map("valid_until")
|
||||
isActive Boolean @default(true) @map("is_active")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@map("fiel_credentials")
|
||||
}
|
||||
|
||||
// ─── Pagos (MercadoPago) ───
|
||||
|
||||
model Subscription {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
plan Plan
|
||||
mpPreapprovalId String? @map("mp_preapproval_id")
|
||||
status String @default("pending")
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
frequency String @default("monthly")
|
||||
currentPeriodStart DateTime? @map("current_period_start")
|
||||
currentPeriodEnd DateTime? @map("current_period_end")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
payments Payment[]
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([status])
|
||||
@@map("subscriptions")
|
||||
}
|
||||
|
||||
model Payment {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
subscriptionId String? @map("subscription_id")
|
||||
mpPaymentId String? @map("mp_payment_id")
|
||||
amount Decimal @db.Decimal(10, 2)
|
||||
status String @default("pending")
|
||||
paymentMethod String? @map("payment_method")
|
||||
paidAt DateTime? @map("paid_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
subscription Subscription? @relation(fields: [subscriptionId], references: [id])
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([subscriptionId])
|
||||
@@map("payments")
|
||||
}
|
||||
|
||||
model SatSyncJob {
|
||||
id String @id @default(uuid())
|
||||
tenantId String @map("tenant_id")
|
||||
type SatSyncType
|
||||
status SatSyncStatus @default(pending)
|
||||
dateFrom DateTime @map("date_from") @db.Date
|
||||
dateTo DateTime @map("date_to") @db.Date
|
||||
cfdiType CfdiSyncType? @map("cfdi_type")
|
||||
satRequestId String? @map("sat_request_id") @db.VarChar(50)
|
||||
satPackageIds String[] @map("sat_package_ids")
|
||||
cfdisFound Int @default(0) @map("cfdis_found")
|
||||
cfdisDownloaded Int @default(0) @map("cfdis_downloaded")
|
||||
cfdisInserted Int @default(0) @map("cfdis_inserted")
|
||||
cfdisUpdated Int @default(0) @map("cfdis_updated")
|
||||
progressPercent Int @default(0) @map("progress_percent")
|
||||
errorMessage String? @map("error_message")
|
||||
startedAt DateTime? @map("started_at")
|
||||
completedAt DateTime? @map("completed_at")
|
||||
createdAt DateTime @default(now()) @map("created_at")
|
||||
retryCount Int @default(0) @map("retry_count")
|
||||
nextRetryAt DateTime? @map("next_retry_at")
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
||||
|
||||
@@index([tenantId])
|
||||
@@index([status])
|
||||
@@index([status, nextRetryAt])
|
||||
@@map("sat_sync_jobs")
|
||||
}
|
||||
|
||||
enum SatSyncType {
|
||||
initial
|
||||
daily
|
||||
}
|
||||
|
||||
enum SatSyncStatus {
|
||||
pending
|
||||
running
|
||||
completed
|
||||
failed
|
||||
}
|
||||
|
||||
enum CfdiSyncType {
|
||||
emitidos
|
||||
recibidos
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// BASES DE DATOS POR TENANT (horux_<rfc>)
|
||||
// Gestionadas por raw SQL via pg Pool
|
||||
// DDL en: apps/api/src/config/database.ts → createTables()
|
||||
// ============================================
|
||||
//
|
||||
// TABLA: rfcs
|
||||
// id SERIAL PRIMARY KEY
|
||||
// rfc VARCHAR(13) UNIQUE NOT NULL
|
||||
// nombre VARCHAR(255)
|
||||
// creado_en TIMESTAMP DEFAULT NOW()
|
||||
// actualizado_en TIMESTAMP DEFAULT NOW()
|
||||
//
|
||||
// TABLA: cfdis
|
||||
// id SERIAL PRIMARY KEY
|
||||
// year VARCHAR(4), month VARCHAR(2)
|
||||
// type VARCHAR(10) -- EMITIDO | RECIBIDO
|
||||
// uuid VARCHAR(36) UNIQUE
|
||||
// serie, folio VARCHAR(50)
|
||||
// status VARCHAR(20) -- Vigente, Cancelado, 0, 1
|
||||
// fecha_emision TIMESTAMP
|
||||
// rfc_emisor, rfc_receptor VARCHAR(13)
|
||||
// nombre_emisor, nombre_receptor VARCHAR(255)
|
||||
// subtotal, total, descuento NUMERIC(18,4) (+ _mxn variants)
|
||||
// moneda VARCHAR(3), tipo_cambio NUMERIC(18,6)
|
||||
// tipo_comprobante VARCHAR(1) -- I, E, T, P, N
|
||||
// metodo_pago VARCHAR(3) -- PUE, PPD
|
||||
// forma_pago VARCHAR(2), uso_cfdi VARCHAR(3)
|
||||
// pac VARCHAR(13), fecha_cert_sat, fecha_cancelacion
|
||||
// uuid_relacionado TEXT
|
||||
// -- Impuestos (cada uno con variante _mxn):
|
||||
// isr_retencion, iva_traslado, iva_retencion, ieps_traslado, ieps_retencion
|
||||
// impuestos_locales_trasladado, impuestos_locales_retenidos
|
||||
// -- Complemento de pagos:
|
||||
// monto_pago, fecha_pago_p, num_parcialidad, saldo_pendiente
|
||||
// isr_retencion_pago, iva_traslado_pago, iva_retencion_pago, ieps_traslado_pago, ieps_retencion_pago
|
||||
// -- Nómina:
|
||||
// num_seguro_social, puesto, salario_base_cot_apor, salario_diario_integrado
|
||||
// total_percepciones, total_deducciones, imp_retenidos_nomina, otras_deducciones_nomina, subsidio_causado
|
||||
// -- Metadata:
|
||||
// conciliado VARCHAR(50)
|
||||
// regimen_fiscal_emisor, regimen_fiscal_receptor VARCHAR(3)
|
||||
// rfc_emisor_id, rfc_receptor_id INTEGER REFERENCES rfcs(id)
|
||||
// xml_url, pdf_url, xml_original TEXT
|
||||
// last_sat_sync TIMESTAMP, sat_sync_job_id UUID
|
||||
// source VARCHAR(20) DEFAULT 'manual'
|
||||
// creado_en, actualizado_en TIMESTAMP
|
||||
//
|
||||
// TABLA: cfdi_conceptos
|
||||
// id SERIAL PRIMARY KEY
|
||||
// cfdi_id INTEGER REFERENCES cfdis(id) ON DELETE CASCADE
|
||||
// clave_prod_serv, no_identificacion, descripcion, cantidad, clave_unidad, unidad
|
||||
// valor_unitario, importe, descuento (+ _mxn)
|
||||
// isr_retencion, iva_traslado, iva_retencion, ieps_traslado, ieps_retencion (+ _mxn)
|
||||
// impuestos_locales_trasladado, impuestos_locales_retenidos (+ _mxn)
|
||||
// total_percepciones, total_deducciones, imp_retenidos_nomina, otras_deducciones_nomina, subsidio_causado (+ _mxn)
|
||||
// creado_en TIMESTAMP
|
||||
//
|
||||
// TABLA: alertas
|
||||
// id UUID PRIMARY KEY DEFAULT gen_random_uuid()
|
||||
// tipo VARCHAR(50), titulo VARCHAR(200), mensaje TEXT
|
||||
// prioridad VARCHAR(20) DEFAULT 'media'
|
||||
// fecha_vencimiento TIMESTAMP, leida BOOLEAN, resuelta BOOLEAN
|
||||
// created_at TIMESTAMP
|
||||
//
|
||||
// ÍNDICES:
|
||||
// idx_cfdis_fecha_emision (DESC), idx_cfdis_type, idx_cfdis_status
|
||||
// idx_cfdis_rfc_emisor, idx_cfdis_rfc_receptor
|
||||
// idx_cfdis_year_month, idx_cfdis_rfc_emisor_id, idx_cfdis_rfc_receptor_id
|
||||
// idx_cfdis_nombre_emisor_trgm (GIN trigram), idx_cfdis_nombre_receptor_trgm (GIN trigram)
|
||||
// idx_cfdi_conceptos_cfdi_id, idx_cfdi_conceptos_clave
|
||||
// EXTENSION: pg_trgm (para búsquedas fuzzy por nombre)
|
||||
250
docs/architecture/deployment.md
Normal file
250
docs/architecture/deployment.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# Guía de Despliegue en Producción - Horux360
|
||||
|
||||
## Infraestructura
|
||||
|
||||
### Servidor
|
||||
- **OS:** Ubuntu 24.04 LTS
|
||||
- **RAM:** 22GB
|
||||
- **CPU:** 8 cores
|
||||
- **Dominio:** horuxfin.com (DNS en AWS Route 53)
|
||||
- **SSL:** Let's Encrypt (certificado real via DNS challenge)
|
||||
- **IP Interna:** 192.168.10.212
|
||||
|
||||
### Stack
|
||||
| Componente | Tecnología | Puerto |
|
||||
|-----------|-----------|--------|
|
||||
| Reverse Proxy | Nginx 1.24 | 80/443 |
|
||||
| API | Node.js + Express + tsx | 4000 |
|
||||
| Frontend | Next.js 14 | 3000 |
|
||||
| Base de datos | PostgreSQL 16 | 5432 |
|
||||
| Process Manager | PM2 | — |
|
||||
|
||||
---
|
||||
|
||||
## Arquitectura de Red
|
||||
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
Nginx (443/SSL)
|
||||
├── /api/* → 127.0.0.1:4000 (horux-api)
|
||||
├── /api/auth/* → 127.0.0.1:4000 (rate limit: 5r/s)
|
||||
├── /api/webhooks/* → 127.0.0.1:4000 (rate limit: 10r/s)
|
||||
├── /health → 127.0.0.1:4000
|
||||
└── /* → 127.0.0.1:3000 (horux-web)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PM2 - Gestión de Procesos
|
||||
|
||||
### Configuración (`ecosystem.config.js`)
|
||||
|
||||
```javascript
|
||||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: 'horux-api',
|
||||
interpreter: 'node',
|
||||
script: '/root/Horux/node_modules/.pnpm/tsx@4.21.0/node_modules/tsx/dist/cli.mjs',
|
||||
args: 'src/index.ts',
|
||||
cwd: '/root/Horux/apps/api',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
autorestart: true,
|
||||
max_memory_restart: '1G',
|
||||
kill_timeout: 5000,
|
||||
listen_timeout: 10000,
|
||||
env: { NODE_ENV: 'production', PORT: 4000 },
|
||||
},
|
||||
{
|
||||
name: 'horux-web',
|
||||
script: 'node_modules/next/dist/bin/next',
|
||||
args: 'start',
|
||||
cwd: '/root/Horux/apps/web',
|
||||
instances: 1,
|
||||
exec_mode: 'fork',
|
||||
autorestart: true,
|
||||
max_memory_restart: '512M',
|
||||
kill_timeout: 5000,
|
||||
env: { NODE_ENV: 'production', PORT: 3000 },
|
||||
},
|
||||
],
|
||||
};
|
||||
```
|
||||
|
||||
### Notas
|
||||
- La API usa `tsx` en lugar de `tsc` compilado porque `@horux/shared` exporta TypeScript raw (ESM) que `dist/` no puede resolver.
|
||||
- Next.js usa la ruta directa `node_modules/next/dist/bin/next` porque `node_modules/.bin/next` es un shell script que PM2 no puede ejecutar como script Node.js.
|
||||
|
||||
### Comandos Útiles
|
||||
```bash
|
||||
pm2 restart all # Reiniciar todo
|
||||
pm2 logs horux-api # Ver logs del API
|
||||
pm2 logs horux-web # Ver logs del frontend
|
||||
pm2 monit # Monitor en tiempo real
|
||||
pm2 save # Guardar estado actual
|
||||
pm2 startup # Configurar inicio automático
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Nginx
|
||||
|
||||
### Archivo: `/etc/nginx/sites-available/horux360.conf`
|
||||
|
||||
#### Rate Limiting
|
||||
| Zona | Límite | Burst | Uso |
|
||||
|------|--------|-------|-----|
|
||||
| `auth` | 5r/s | 10 | `/api/auth/*` |
|
||||
| `webhook` | 10r/s | 20 | `/api/webhooks/*` |
|
||||
| `api` | 30r/s | 50 | `/api/*` (general) |
|
||||
|
||||
#### Security Headers
|
||||
- `Content-Security-Policy`: Restrictivo (`default-src 'self'`)
|
||||
- `Strict-Transport-Security`: 1 año con includeSubDomains
|
||||
- `X-Frame-Options`: SAMEORIGIN
|
||||
- `X-Content-Type-Options`: nosniff
|
||||
- `Permissions-Policy`: camera, microphone, geolocation deshabilitados
|
||||
- `Referrer-Policy`: strict-origin-when-cross-origin
|
||||
|
||||
#### Body Limits
|
||||
- Global: `50M` (Nginx)
|
||||
- API default: `10mb` (Express)
|
||||
- `/api/cfdi/bulk`: `50mb` (Express route-specific)
|
||||
|
||||
### Renovar SSL
|
||||
```bash
|
||||
certbot renew --dry-run # Verificar
|
||||
certbot renew # Renovar
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## PostgreSQL
|
||||
|
||||
### Configuración de Rendimiento (`postgresql.conf`)
|
||||
| Parámetro | Valor | Descripción |
|
||||
|-----------|-------|-------------|
|
||||
| `max_connections` | 300 | Para multi-tenant con pools por tenant |
|
||||
| `shared_buffers` | 4GB | ~18% de 22GB RAM |
|
||||
| `work_mem` | 16MB | Memoria por operación de sort/hash |
|
||||
| `effective_cache_size` | 16GB | ~72% de RAM |
|
||||
| `maintenance_work_mem` | 512MB | Para VACUUM, CREATE INDEX |
|
||||
| `wal_buffers` | 64MB | Write-ahead log buffers |
|
||||
|
||||
### Arquitectura Multi-Tenant
|
||||
Cada cliente tiene su propia base de datos PostgreSQL:
|
||||
```
|
||||
horux360 ← Base central (tenants, users, subscriptions)
|
||||
horux_cas2408138w2 ← Base del admin global
|
||||
horux_<rfc> ← Base de cada cliente
|
||||
```
|
||||
|
||||
### Backups
|
||||
```bash
|
||||
# Cron job: 0 1 * * * /root/Horux/scripts/backup.sh
|
||||
# Ubicación: /var/horux/backups/
|
||||
# Retención: 7 diarios + 4 semanales
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Variables de Entorno
|
||||
|
||||
### API (`apps/api/.env`)
|
||||
```env
|
||||
NODE_ENV=production
|
||||
PORT=4000
|
||||
DATABASE_URL="postgresql://postgres:<password>@localhost:5432/horux360?schema=public"
|
||||
JWT_SECRET=<min 32 chars>
|
||||
JWT_EXPIRES_IN=15m
|
||||
JWT_REFRESH_EXPIRES_IN=7d
|
||||
CORS_ORIGIN=https://horuxfin.com
|
||||
FRONTEND_URL=https://horuxfin.com
|
||||
FIEL_ENCRYPTION_KEY=<min 32 chars, REQUERIDO>
|
||||
FIEL_STORAGE_PATH=/var/horux/fiel
|
||||
|
||||
# MercadoPago
|
||||
MP_ACCESS_TOKEN=<token>
|
||||
MP_WEBHOOK_SECRET=<secret, REQUERIDO para producción>
|
||||
|
||||
# SMTP (Google Workspace)
|
||||
SMTP_HOST=smtp.gmail.com
|
||||
SMTP_PORT=587
|
||||
SMTP_USER=ivan@horuxfin.com
|
||||
SMTP_PASS=<app-password>
|
||||
SMTP_FROM=Horux360 <ivan@horuxfin.com>
|
||||
|
||||
# Admin
|
||||
ADMIN_EMAIL=carlos@horuxfin.com
|
||||
```
|
||||
|
||||
### Web (`apps/web/.env.local`)
|
||||
```env
|
||||
NEXT_PUBLIC_API_URL=https://horuxfin.com/api
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Directorios Importantes
|
||||
|
||||
```
|
||||
/root/Horux/ ← Código fuente
|
||||
/var/horux/fiel/ ← Archivos FIEL encriptados (0700)
|
||||
/var/horux/backups/ ← Backups de PostgreSQL
|
||||
/etc/nginx/sites-available/ ← Config de Nginx
|
||||
/etc/letsencrypt/live/ ← Certificados SSL
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Despliegue de Cambios
|
||||
|
||||
```bash
|
||||
# 1. Pull cambios
|
||||
cd /root/Horux
|
||||
git pull origin main
|
||||
|
||||
# 2. Instalar dependencias
|
||||
pnpm install
|
||||
|
||||
# 3. Build
|
||||
pnpm build
|
||||
|
||||
# 4. Reiniciar servicios
|
||||
pm2 restart all
|
||||
|
||||
# 5. Si hay cambios en nginx:
|
||||
cp deploy/nginx/horux360.conf /etc/nginx/sites-available/horux360.conf
|
||||
nginx -t && systemctl reload nginx
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### API no inicia
|
||||
```bash
|
||||
pm2 logs horux-api --lines 50 # Ver logs de error
|
||||
pm2 restart horux-api # Reiniciar
|
||||
```
|
||||
|
||||
### Puerto en uso
|
||||
```bash
|
||||
lsof -i :4000 # Ver quién usa el puerto
|
||||
kill <PID> # Matar proceso
|
||||
pm2 restart horux-api
|
||||
```
|
||||
|
||||
### Certificado SSL expirado
|
||||
```bash
|
||||
certbot renew
|
||||
systemctl reload nginx
|
||||
```
|
||||
|
||||
### Base de datos lenta
|
||||
```bash
|
||||
sudo -u postgres psql -c "SELECT * FROM pg_stat_activity WHERE state = 'active';"
|
||||
```
|
||||
Reference in New Issue
Block a user