1. Re-captura: el dedup por telefono/email ya no considera etapas cerradas (Descartado, Perdido, Cancelo, Cancelacion Apartado). Los restos de la ultima limpieza (17 filas de APARTADO cancelados sin contraparte activa) bloqueaban a los vendedores al re-capturar; los registros activos siguen bloqueando con 409 como siempre. 2. Bitacora compartida: GET /recordatorios con leadId/clienteClave devuelve las notas de TODOS los usuarios de ese cliente (antes cada quien solo veia las suyas). Mi dia pide la bitacora con esos filtros; la lista personal sigue siendo propia. 3. Origen en oportunidades: el resumen de mi-dia ya devuelve origen y la card de cada oportunidad lo muestra (telefono · desarrollo · canal · modelo). 4. Privacidad del buscador: /leads/buscar y /buscar filtran por dueno cuando el rol es vendedor (leads por usuarioId, contactos por capturadoPorId, cotizaciones por vendedorId). Los roles de supervision siguen viendo todo. Ya no aparecen leads de otros asesores con nombre y telefono al teclear. Verificado en vivo contra el API: 10/10 comprobaciones (re-captura del telefono cancelado 201, duplicado activo 409, nota del admin visible para la vendedora, origen presente en 27/27 oportunidades, buscador del vendedor sin datos ajenos y del admin completo). 716 pruebas del API y 32 de mi-dia en verde. Los tests de roles ahora usan prisma mockeado (la BD viva cambia cuando el cliente edita permisos desde la UI).
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.