El controller de config/roles ya devuelve modulos (array) y acciones (objeto) parseados, pero el frontend intentaba JSON.parse() otra vez sobre algo que no era string. Ahora verifica el tipo antes de parsear.
FRASA Ecosystem
Ecosistema comercial de Frasa Desarrollos S.A.P.I. de C.V.
Componentes
| Componente | Ruta | Descripción |
|---|---|---|
apps/landing |
/ |
Entrada al ecosistema |
apps/cotizador |
/quote/ |
Cotizador financiero (4 propuestas, PDF ANEXO A) |
apps/alta-clientes |
/client/ |
Alta de clientes (wizard, firma digital, PDFs PLD) |
apps/cobranza |
/pay/ |
Planes de enganche, vencidos, estados de cuenta |
apps/dashboards |
/dash/ |
Tableros KPI (leads, oportunidades, inventario, cobranza) |
apps/mi-dia |
/hoy/ |
Agenda del vendedor (mis leads, actividades, acciones CRM) |
apps/agente-ia |
/ai/ |
Asistente IA con documentos del ecosistema |
api-gateway |
/api/ |
Express 5 + Prisma + integración Monday.com |
PWA (instalabilidad básica)
Las 7 apps son instalables (manifest + íconos): cada una tiene
public/manifest.webmanifest ("Frasa · <app>", display: standalone,
theme_color/background_color) e íconos 192/512 en public/icons/
(generados con scripts/generar-iconos-pwa.py, venv /root/.venv-tools).
Decisión: NO hay service worker. Solo se busca instalabilidad básica (ícono en el teléfono que abre la app a pantalla completa). Un SW con caché arriesga servir bundles viejos tras cada deploy (los usuarios son vendedores en campo; no van a limpiar caché a mano). Si algún día se quiere modo offline, hay que diseñar primero la estrategia de invalidación de caché.
Desarrollo local
Cada app es un proyecto Vite independiente: npm install && npm run dev (o npm run preview tras npm run build). Tests: npx vitest run en cada paquete. El API: cd api-gateway && npm install && npx prisma migrate dev && npm run dev.
Variables de entorno: copiar .env.example a .env y completar (el .env nunca se commitea).
Servidor actual (LXC sin Docker)
Esta máquina es un contenedor LXC sin nesting: Docker no puede correr contenedores. El stack corre directo en el host:
- NGINX (apt) sirve los
dist/de las apps y proxifica/api/→127.0.0.1:3000. Config:deploy/nginx-host.conf→/etc/nginx/sites-available/frasa. - API como servicio systemd:
deploy/frasa-api.service→frasa-api.service(systemctl status frasa-api, logs en/var/log/frasa-api.log). - Rebuild de apps tras cambios:
VITE_API_URL=/api npm run builden cada app +systemctl reload nginx(no requiere restart).
Acceso público: Cloudflare Tunnel
El stack se expone en https://frasa.consultoria-as.com mediante Cloudflare Tunnel (Zero Trust), sin abrir puertos ni depender del DDNS:
- Túnel administrado desde el dashboard (Zero Trust → Networks → Tunnels);
el ingress vive en Cloudflare: public hostname
frasa.consultoria-as.com→http://localhost:80. No hayconfig.ymllocal. - Servicio systemd
cloudflared(instalado concloudflared service install <token>, token en/etc/cloudflared/token). SSL lo termina Cloudflare automáticamente; Certbot ya no aplica en este servidor. - NGINX de host sigue en puerto 80 con
server_name _(catch-all), así que cualquier hostname del túnel funciona sin cambios. - Las ligas de firma remota se construyen con
window.location.origin, así que ya salen con el dominio público al usarlas desde ahí.
El docker-compose.yml queda listo para cuando el stack se mude a una VM con Docker.
Deploy con Docker (VM futura)
git clone https://git.consultoria-as.com/consultoria-as/FRASA-CAS.git /opt/frasa-ecosystem
cd /opt/frasa-ecosystem
cp .env.example .env && nano .env # completar secretos
# SSL: webroot (funciona con nginx corriendo y renueva sin downtime)
docker compose up -d nginx # primer arranque SIN ssl: ver nota abajo
certbot certonly --webroot -w /var/www/certbot -d frasadesarrollos.ddns.net
docker compose build && docker compose up -d
docker compose logs -f api
curl https://frasadesarrollos.ddns.net/api/health
Modo local sin SSL (LAN): .env con NGINX_CONF=nginx.http.conf hace que NGINX
sirva todo por HTTP en el puerto 80. Cuando el DDNS apunte a este servidor:
certbot certonly --webroot -w /var/www/certbot -d frasadesarrollos.ddns.net,
quita NGINX_CONF del .env (vuelve nginx.conf con 443) y
docker compose restart nginx. Renovación: certbot renew --deploy-hook "docker compose -f /opt/frasa-ecosystem/docker-compose.yml restart nginx".
Backup: scripts/backup.sh (cron sugerido 17 2 * * *).
Monday.com
Workspace Frasa-CAS, organizado en carpetas como el CRM original del cliente:
- CRM: Leads, Oportunidades, Apartado, Actividades
- Ventas: Unidades (inventario), Ventas, Flujo Escrituras, Saldos a Favor, Cobranza, Seguimiento Preventa
- Cobranza (board 18429260549) y Seguimiento Preventa (board 18429260580): estructura replicada del CRM anterior, sin flujo automático por ahora. Creados por API; pendiente moverlos a la carpeta Ventas (folder 21263260) a mano en la UI.
- Postventa: Comunicación (bitácora postventa: unidad, canal, estado, fecha)
- Generales: Contactos, Motivos No Compra, Desarrollos
Los board IDs están en .env.example. Webhook: POST /api/webhooks/monday (responde el challenge de activación).
Board Unidades — columnas de obra/estatus
Alimentadas por api-gateway/scripts/sync-obra-estatus.py desde el Reporte_EstatusVenta.xlsx del cliente (idempotente, --dry-run disponible; ver docstring del script):
% Avance obra,Meses atraso(numbers),Contratista,Clave catastral,No. oficial,Vendedor,Broker(text),Venc. autorización(date).Fecha entrega obra(date): la columna originalFecha entregaya traía datos de habitabilidad, por eso se creó una nueva.- Hitos documentales (checkbox):
Cert. Gas,TO / C4,Régimen inscrito,Lista avalúo. Se encienden cuando la fecha del hito en el Excel ya pasó; el script nunca los apaga. - Nota: en
No. oficial/Clave catastralel Excel del cliente trae fechas programadas (no números oficiales); se cargan como texto ISO tal cual.
Boards operativos nuevos
- Flujo Escrituras: un item por mes/semana/proyecto con
Escrituras(numbers) eIngreso(numbers) del Flujo Calendario del cliente. Idempotencia por nombre único (AGOSTO SEM 3 AL 7 - SOLE). - Saldos a Favor: name
CLIENTE / UNIDAD; columnasDesarrollo(status),Unidad(text),Monto(numbers),Estado(Pendiente/Devuelto),Comentario. Idempotencia por Unidad+Desarrollo.
Las unidades SOLE y MUTUO no existen en el board Unidades: el script las reporta como saltadas (sus vendedor/broker/venc. autorización no se cargan por ahora).
Pendientes conocidos
- Migrar Prisma de SQLite a Postgres 16 (dinero a
Decimal). - Auth JWT, helmet, rate-limit, CORS restringido (hoy: red interna).
- Self-hostear worker/wasm de tesseract.js (OCR sin internet).
- Automatizaciones Monday (round-robin, recordatorios 10 días) y cron de cobranza en el API.