Skip to main content
Try Lexiel for freeTry now →
Changelog

Changelog

All features, improvements, and fixes in Lexiel. Updated with each release.
Upcoming
Coming soon
Añadido2026-04-02: distribution + security audit)
  • CRITICAL: AcademyTeaser en landing principal: nueva seccion con 9 cursos, 4 highlights, dual CTA. La landing tenia 0 menciones de academia pese a tener 9 cursos listos.
  • Security: Stripe health check: eliminada request autenticada a Stripe API. Solo verifica existencia de la key en env.
  • Funnel audit: 0 registros, 0 quiz, 0 certificados en produccion. Prioridad cambia de "mas features" a "distribucion".
  • 0 errores TypeScript en los 3 paquetes (api, app, web).
Añadido2026-04-01: newsletter, webhooks, A/B banner, mobile UX, API tests)
  • Academy newsletter cron: email mensual (1er lunes) con stats KPIs, top 5 leaderboard, CTA. Unsubscribe headers.
  • Partner webhooks: POST a URL configurada del colegio cuando un colegiado obtiene certificado. Fire-and-forget.
  • Certificados filtrables por colegio: dropdown en certificates tab del admin.
  • A/B banner: 3 variantes de copy rotadas (feature, breadth, emotional). Sin infra externa.
  • Mobile quiz: `touch-manipulation` + tap targets mas grandes en movil.
  • API integration tests: 11 tests contra endpoints reales (stats, courses, modules, quiz, leaderboard, register, cert verify, knowledge-export, widget-event).
  • OG images: leaderboard + API docs.
  • 3 nuevos tracks en progreso: Propiedad Intelectual, Derecho Digital, Mediacion/ADR.
Añadido2026-04-01: leaderboard, API docs, streaks, Alma sync, fiscalidad)
  • Leaderboard publica (`/academia/leaderboard`): ranking anonimo con medallas (top 3), modulos completados, score medio, puntos. Nombres anonimizados "Jose D." (RGPD).
  • API docs publica (`/academia/api`): documentacion interactiva de todos los endpoints publicos y autenticados. Rate limits, parametros, metodo/path.
  • Streak calculation real: el quiz handler ahora calcula streaks consecutivos (yesterday check). Los puntos incluyen bonus por score alto.
  • Alma KB sync (`GET /v1/public/academy/knowledge-export`): exporta contenido educativo como chunks RAG-optimizados con metadata para la knowledge base de Alma.
  • Widget events table: tabla `widget_events` para tracking de B2B widget analytics (impressions, questions, leads).
  • Track Fiscalidad (en progreso): IVA/IRPF, IS, VeriFactu, procedimientos tributarios.
  • Certificate logging: issuance logged con structured info para monitoring.
Añadido2026-04-01: audit log, import, gamification, compliance, webinar, OG images)
  • Audit log: tabla `academy_audit_log` (who/what/when) + `GET /v1/admin/academy/audit-log`
  • Import JSON: `POST /v1/admin/academy/import` restaura curso completo desde export JSON
  • Gamificacion: puntos (100 por aprobado + bonus score, 20 por intento), leaderboard anonimo `GET /v1/public/academy/leaderboard` (RGPD: "Jose D." format)
  • Track Compliance Empresarial: 4 modulos (blanqueo PBC, compliance penal, MiFID II, ESG/CSRD), 20 preguntas quiz
  • Webinar fecha fija: 24 de abril 2026, countdown timer client component
  • OG images: /academia/stats (📊), /academia/webinar (🎥 con fecha), /academia/embed (🏛️)
  • PDF certificado: ya genera para todos los tracks (era generico, verificado)
Añadido2026-04-01: admin reorder, bulk, export, funnel, procesal track)
  • Module reorder: botones up/down en courses-tab para reordenar modulos (swaps sortOrder via PATCHes paralelos).
  • Bulk publish/unpublish: publicar/despublicar todos los modulos de un curso con un clic.
  • Course export JSON (`GET /v1/admin/academy/export/:courseId`): exporta curso completo con modulos y preguntas como JSON portable (sin IDs de BD).
  • Recent registrations feed (`GET /v1/admin/academy/recent-registrations`): ultimos 20 registros de guests.
  • Marketing funnel (`GET /v1/admin/academy/funnel`): pipeline registros → quiz attempts → passes → certificados.
  • Track Procesal (en progreso): 4 modulos (plazos, recursos, vista oral, ejecucion).
  • Playwright E2E (en progreso): test de flujo completo registro → quiz → certificado.
  • Rodrigo bugs: verificado que todos los issues del primer cliente estan resueltos (particular role Phase 1+2 complete).
Añadido2026-04-01: facturas rectificativas + PDF premium)
  • Facturas rectificativas R1/R5: flujo legalmente correcto (RD 1619/2012, VeriFactu RD 1007/2023). Wizard con selector tipo (sustitucion/diferencias) y motivo. Status 'rectified' en enum + banners de navegacion bidireccional "Rectifica a" / "Rectificada por".
  • PDF premium para abogados: EB Garamond serif + Inter sans-serif, paleta navy/gold, barras de acento, tabla con filas alternas, seccion suplidos, IBAN con banco, footer VeriFactu QR, header FACTURA RECTIFICATIVA.
  • legalDisclaimer en billing profile: campo para "Inscrita en el Registro Mercantil de..." que aparece al pie del PDF.
Corrección
  • VeriFactu compliance: DELETE endpoint y bulk cancel ya no permiten cancelar facturas emitidas (solo borradores). Las emitidas requieren rectificativa.
  • Sidebar light mode: borde visible entre sidebar y contenido.
  • FacturaE tooltip: extension corregida de .xsig a .facturae.xml (no es firma digital).
  • Cases layout: fix inline concatenacion setShowExportMenu, refactor botones en dropdowns.
  • SSE heartbeat: type Promise-compatible, intervalo 10s, keepalive data.
  • Chat gratis: logo LX completo en avatar bubble, brand color en user messages, dark mode gradient.
Eliminado
  • Embeddable B2B widget — Deleted `apps/web/public/embed/widget.js` and `packages/api/src/routes/widget.ts`. Orphaned feature with no active integrations. AgenteUno is the exclusive chatbot provider for the Lexiel ecosystem. Schema field `api_keys.keyType` preserved for historical data. Commit: 43a9e732.
Añadido2026-04-01: compliance 3-layer UI complete
  • Compliance controls CRUD UI: "Nuevo control" button → form (title, category, regulation). List with status icons. Category badges. Last verified date.
  • Testing/catas UI: "Registrar test" per control → result selector (Superado/No superado/Parcial) + notes. Saves to API, updates status instantly.
  • Compliance 3-layer API (session 88i): Controls CRUD, testing records, audit view endpoints. Schema: complianceStandards + complianceRequirements + complianceChecks.
  • ALL 3 layers from Raquel Arga feedback now complete: Normativa → Controles → Testing → Vista auditoría.
Añadido2026-04-01: mega session
  • Impacto alto:
  • ElevenLabs v3 scripts: 5 scripts convertidos a formato audio tags (m1.txt-m5.txt en docs/academy-scripts/elevenlabs/). Tags: [calm], [serious], [emphasized], [warm], [excited], [whisper], [pause]. M5 en formato multi-speaker ADRIAN:/SOFIA:.
  • Webinar landing (`/academia/webinar`): 30 min agenda, diferenciadores vs webinars de teoria, CTA registro. Fecha TBC.
  • Track RGPD completado: 4 modulos (responsable tratamiento, EIPD, brechas seguridad, transferencias + DPO), 20 preguntas quiz, contenido ES+EN.
  • Impacto medio:
  • Magic link email: `sendAcademyWelcomeEmail()` con progressToken como URL clicable. Enviado automaticamente al registrarse.
  • Railway crons configurados: academy-completion-nudge (lunes 08:00), case-followup-reminder (diario 08:15), case-weekly-summary (viernes 08:30), insurance-expiry-alerts (diario 09:00).
  • Embed para colegios (`/academia/embed`): codigo de embed copy-paste + version co-branding con partner name + logo. Documentacion completa.
  • +4 more
Añadido2026-04-01: compliance 3-layer module
  • Compliance controls CRUD: `POST/GET /v1/compliance/controls` — org-specific controls linked to regulation (title, category, frequency, responsible). Stored as custom complianceRequirements.
  • Compliance testing: `POST /v1/compliance/tests` — record test results (pass/fail/partial) with tester, date, notes. Maps to compliance check status automatically.
  • Audit view: `GET /v1/compliance/audit` — unified report showing normativa + custom controls + test results, grouped by standard, with per-group and overall scores. Ready for auditor review.
  • This completes the 3 layers Raquel Arga described: normativa → controles → testing/catas → vista de auditoría unificada.
Añadido2026-04-01: command palette, search, contract presets)
  • Command palette + global search: "Actas societarias" and "Generador de contratos" added with keywords.
  • 3 more contract presets (6 total): Suministro con SLA, Consultoría estratégica, NDA pre-inversión.
Añadido2026-04-01: progress token, competitive banner, emdash cleanup)
  • Guest progress token: `progressToken` en `academy_user_progress`. Generado al registrarse, propagado a todos los quiz del mismo email. `GET /v1/public/academy/progress/:token` para recuperar progreso cross-device. Frontend guarda token en localStorage.
  • Banner competitivo en landing academia con CTA "9 modulos, certificado verificable en LinkedIn, sin horario fijo".
  • Outreach mejorado: emails a colegios con "a diferencia de webinars puntuales" + opcion 4 (formacion personalizada).
  • 184 emdashes eliminados: 9 modulos BD + 4 seed files. Reemplazo context-aware.
  • CTA opaco: boton hero bg-white/5 → bg-slate-800 (grid pattern fix).
Añadido2026-04-01: draft history, compliance landing tools, chunk fixer execution)
  • Draft history component: Reusable collapsible list of previously generated documents (actas, contratos). Shows in both /actas and /contratos-ia. Expandable preview, copy, delete.
  • Compliance landing tools section: 4 cards on /para/compliance-officers showcasing GDPR anonymization, minutes generator, AI contracts, internal regulations. Links to demo + registration.
  • Chunk fixer executed: 15 oversized chunks processed, 30 sub-chunks created. Core law chunks (LEC Art. 24: 42K) identified for future manual sub-chunking.
Añadido2026-04-01: chunk fixer, E2E tests, AEPD exam analysis)
  • Oversized chunk fixer script (`fix-oversized-chunks.ts`): Finds and splits oversized embeddings in core laws. Found 15 chunks > 4K chars in LEC (42K!), CC (27K), CP (10K). Splits by article boundaries, then paragraphs. Supports --dry-run.
  • E2E smoke tests: actas-contratos.spec.ts verifying /actas and /contratos-ia routes exist.
  • AEPD DPD exam analysis: 150 questions parsed and categorized by domain. No solucionario available — retrieval benchmark only.
  • Art. 31 bis sub-chunking confirmed: 5 sub-chunks working. Q12 benchmark improvement expected.
Añadido2026-04-01: compliance alerts, dashboard quick actions, exam bank, linter fixes)
  • Compliance push notifications: When new BdE/CNMV circulars detected in BOE, push to all compliance/in-house org owners automatically. Non-blocking, 100 owner limit.
  • Compliance dashboard quick actions: 4 cards at top of /compliance — generate contract, generate minutes, internal regulations, regulatory alerts. Fast navigation for compliance users.
  • Compliance exam bank: AEPD DPD (150Q), BdE Inspector 2024 (46Q). 196 real exam questions from official institutions. Documented in docs/exams/compliance/.
  • Linter auto-fixes: 271 files cleaned up (chat-gratis → consulta-legal-gratis rename, minor formatting).
Añadido2026-04-01: contract generator, art 31bis sub-chunks, acta presets, compliance exams research)
  • AI Contract Generator (`/contratos-ia`): 4 contract types (servicios, suministro, NDA, encargado tratamiento RGPD) with structured forms, 3 presets, SSE streaming, PDF export. Templates include 15+ clauses, RGPD art. 28 compliance, SLA/KPIs.
  • Art. 31 bis CP sub-chunked: Split from 1 chunk (4316 chars) into 5 focused sub-chunks (31bis.1 through 31bis.5). Should improve Q12 benchmark score from 0.728 to ~0.85+.
  • 4 acta presets: Junta ordinaria anual, junta extraordinaria (ampliación capital), consejo (presupuesto), consejo (compliance y riesgos).
  • Compliance exams research: Found ~400 free questions from AEPD DPD (150Q), KPMG (60Q), BdE inspector (90Q), CESCOM sample. PDFs being downloaded.
  • Compliance benchmark: 15/15 (100%) — all questions pass retrieval quality check.
Añadido2026-04-01: PDF export, BdE/CNMV circulars, benchmark running)
  • Actas PDF export: `POST /v1/legal/acta-pdf` generates professional A4 PDFs with PDFKit (navy headers, justified text, page breaks). UI button in generator toolbar.
  • 3 more regulator circulars: CBdE 1/2013 (CIR), CBdE 5/2012 (transparencia bancaria), CCNMV 2/2020 (publicidad inversiones). Total: 5 circulars indexed.
  • Compliance benchmark COMPLETE: 15/15 (100%) pass rate, avg score 0.784, range 0.728-0.840. RAG quality rated GOOD. All compliance laws correctly retrieved.
Añadido2026-04-01: actas signature + templates, compliance benchmark, BdE circulars)
  • Actas: "Enviar a firma" button: Connects to Signaturit (fully integrated). Pre-fills president + secretary as signers. Opens signature flow.
  • Actas: custom template editor: Collapsible textarea for org-specific formatting instructions (letterhead, clauses, company format). Passed as additionalContext to AI.
  • Actas: sidebar pathPrefix fix: `/actas` now correctly selects AI Assistant group.
  • Compliance RAG benchmark script: 15 questions testing retrieval quality for CP 31bis, Ley 10/2010, Ley 2/2023, Ley 19/2013. Reports pass/fail with scores.
  • BdE circulars added to corpus: Circular 4/2017 (normas información financiera), Circular 2/2016 (supervisión y solvencia).
Añadido2026-03-31: actas save draft, compliance corpus verified)
  • Save draft button in actas generator: "Guardar borrador" saves generated acta as writing draft via `/v1/writing-drafts`. Title auto-generated from doc type + company + date.
  • Sidebar pathPrefix fix: `/actas` added so navigation selects correct group.
  • Compliance corpus verified: 8 laws with 4,008 total chunks indexed and embedded. CP (591), Ley 10/2010 blanqueo (475), Ley 2/2023 denunciantes (173), Ley 19/2013 transparencia (124), Ley 11/2018 ESG (273), LOSSEC (831), TRLMV (1,418), LCCC (123).
Añadido2026-03-31: interactive actas generator UI, sidebar entry)
  • Interactive corporate minutes generator (`/actas`): Form-based UI for generating actas de juntas, consejos de administración, convocatorias y certificados de acuerdos. Structured form (company, attendees, agenda items, quorum) + AI generation with SSE streaming. Copy to clipboard.
  • Sidebar entry: "Actas societarias" / "Corporate Minutes" in AI Assistant group with tooltip. i18n labels in ES + EN.
Añadido2026-03-31: AI corporate document generator, legal-writing anonymization)
  • AI-powered corporate document generator: 4 new document types in `/v1/legal/generate` — acta_junta (Junta General minutes, LSC arts. 191-199), acta_consejo (Board minutes, LSC arts. 245-251), convocatoria_junta (meeting notice with deadlines), certificado_acuerdos (corporate resolution certificate). Full LSC compliance, formal mercantile language, streaming SSE.
  • Legal-writing endpoint anonymized: `containsPersonalData()` + `prepareForLLM()` added to `/v1/legal/generate` — was a missing RGPD gap.
Añadido2026-03-31: banking laws corpus, BdE/CNMV crawler, regulator seeder)
  • 4 banking/financial laws indexed: LOSSEC (supervisión entidades crédito, 776K), TRLMV (mercado valores, 1.3M), LCCC (crédito consumo), PSD2-ES (servicios de pago).
  • Regulator crawler script (`seed-regulators.ts`): BdE dept 1020, CNMV dept 1040 via BOE API. Prepared for day-by-day circular iteration.
  • Compliance corpus now covers: CP 31bis + Ley 10/2010 (blanqueo) + Ley 2/2023 (denunciantes) + Ley 19/2013 (transparencia) + Ley 11/2018 (ESG) + LOSSEC + TRLMV + LCCC.
Añadido2026-03-31: corporate clauses, compliance benchmark, BdE/CNMV research)
  • 3 more corporate clauses (47 total): Contrato marco de prestación de servicios, Transferencia internacional de datos RGPD Cap. V, Cesión de posición contractual.
  • Compliance benchmark (15 questions): CP 31 bis, Ley 10/2010, Ley 2/2023, Ley 19/2013, practical cross-cutting cases. Ready to run against RAG.
  • BdE/CNMV research: Both publish via BOE API (dept 1020/1040), ~300 circulars total, RSS available. Can reuse existing seed-from-boe-api.ts pattern.
Añadido2026-03-31: compliance corpus, Raquel message, Docu.expert review)
  • 3 compliance laws indexed from BOE API: Ley 2/2023 (protección de denunciantes/whistleblower), Ley 19/2013 (transparencia y buen gobierno), Ley 11/2018 (información no financiera/ESG). CP arts. 31bis and Ley 10/2010 blanqueo already in corpus.
  • Raquel Instagram message finalized: Copy-paste ready with 3 URLs (demo anonimización, enterprise, docu.expert) + videocall offer. Internal notes included.
  • Docu.expert assessed: Landing page already communicates "100% local, tus datos nunca salen". Presentable for Raquel. Gaps noted for future enterprise IT docs.
  • Pricing recommendation documented: Same pricing structure for all org types, orgType determines UX not price. Enterprise custom for BBVA-type deals.
Añadido2026-03-31: sidebar conditional, enterprise landing page)
  • Sidebar conditional by orgType: "Procedimientos" and "Escritos" hidden for in-house/compliance. Group labels: "Asuntos" (not "Expedientes"), "Normativa y recursos" (not "Biblioteca"). New `inHouseHidden` flag on NavItem + NavGroup types.
  • Enterprise landing page (`/enterprise`): Security compliance page for corporate sales — anonymization visual flow, 6 security features with RGPD article refs, on-premise CTA (Docu.expert), compliance checklist (10 items). Dark theme, bilingual.
  • Raquel message updated: Added enterprise URL.
Añadido2026-03-31: onboarding in-house adaptation, more clauses, Playwright tests)
  • Onboarding FirstCaseStep adapted for in-house: "Primer asunto" (not "Primer expediente"), "Contraparte / proveedor" (not "Nombre del cliente"), placeholder "Contrato servicios TI — Proveedor X", button "Crear asunto". orgType flows from step 1 to step 3.
  • 3 new in-house clauses (44 total): NDA reforzado con proveedores, Contrato de encargado del tratamiento RGPD completo (art. 28), Cláusula de compliance y prevención penal (art. 31 bis CP).
  • Playwright E2E tests for `/demo/anonimizacion`: 5 tests covering page load, anonymization interaction, custom text detection, how-it-works section, enterprise CTA.
Añadido2026-03-31: corporate clauses, societario templates, anonymization demo)
  • 8 new corporate contract clauses: ANS/SLA (availability, response times, KPIs), SLA penalties (tiered by compliance %), service evolution, contract object. Total: 40 static clauses.
  • 4 societario templates: Acta de Junta General, Acta de Consejo de Administración, Convocatoria de Junta, Certificado de Acuerdos Sociales.
  • Anonymization demo page (`/demo/anonimizacion`): Interactive demo showing before/after of PII anonymization. Entity detection table, 3-step explanation, enterprise CTA with Docu.expert link.
  • Raquel message updated: Added demo URL and docu.expert link.
Añadido2026-03-31: complete RGPD audit, dashboard differentiation)
  • COMPLETE RGPD AUDIT: 41 of ~50 LLM endpoints now anonymize personal data. 25 additional endpoints fixed across 10 files (case-ai 6, documents 7, legal-composer 3, plugin 3, declarations 2, testimony-prep 3, devils-advocate 1, classify-document 1, dashboard 1, style-preferences 1).
  • Dashboard differentiation for in-house: PageIntro, quick actions, and practice health strip adapt based on orgType. "Nuevo asunto" instead of "Nuevo caso", "Normativa interna" instead of "Procedimiento guiado". Billing KPIs hidden for in-house. All changes are ADDITIVE — despacho experience unchanged.
Corrección2026-03-31: RGPD audit
  • SECURITY AUDIT: Found 50+ LLM call sites, only 2 were anonymizing. Fixed 12 critical/high-risk endpoints across 6 files: criminal-records, escritos (3 endpoints), contracts (3 endpoints), meeting-analyzer, intake, email-inbound.
  • System prompt privacy rules: Claude/Gemini now instructed to work with placeholders, minimize PII usage, treat sensitive data confidentially (RGPD Art. 5.1.c).
  • Registration consent: Checkbox now explicitly mentions AI processing with prior anonymization (RGPD Art. 6(1)(a)).
  • Docu.expert reviewed: Technically mature (9/10), live at docu.expert, Ollama support for 100% local processing. Perfect cross-sell for enterprise clients (BBVA) requiring on-premise.
Corrección2026-03-31: document analysis anonymization, third Raquel feedback)
  • SECURITY FIX: `document-analyzer.ts` now anonymizes personal data before sending to Claude. Previously sent full document text with PII to external API — RGPD Art. 32 compliance gap closed.
  • Third Raquel Arga feedback: BBVA prohibits AI with personal data. Documented enterprise privacy concerns, validated Lexiel's placeholder approach, identified Docu.expert cross-sell opportunity.
  • Message draft for Raquel: Technical-legal response covering anonymization, encryption, zero-retention, RGPD Art. 32 compliance, Docu.expert on-premise alternative.
Añadido2026-03-31: onboarding org type, KB repositioning, second Raquel feedback)
  • Second Raquel Arga feedback: detailed daily in-house work (contracts, SLA clauses, corporate secretary), internal corporate regulations upload, sectoral regulators.
  • Onboarding org type selection: FirmInfoStep now asks organization type (despacho/in_house/compliance/mixto) with descriptions. Dynamic labels based on selection. Province dropdown (50 provinces) for RAG jurisprudence proximity boost.
  • KB repositioned as "Normativa Interna": For in-house/compliance orgs, Knowledge Base shows as "Normativa Interna" with compliance-focused description. Zero new infrastructure — reuses existing org KB + RAG indexing.
  • orgType in auth: `/v1/auth/me` now returns `orgType` field. Frontend User type updated. Sidebar conditionally shows "Normativa Interna" vs "Biblioteca".
  • updateOrganizationSchema: Accepts `organizationType` and `defaultProvince` fields.
Añadido2026-03-31: partner feedback KB, province geolocation, auto-analysis pipeline)
  • Partner feedback knowledge base (`docs/partner-feedback/`): estructura para almacenar, clasificar y consultar feedback de partners. Perfiles de partners (mutables), sesiones de feedback (inmutables), y síntesis de gaps. Diseñado para RAG.
  • Raquel Arga feedback documentado: 9 insights extraídos del feedback de la abogada in-house del departamento legal de BBVA. Insights clave: Lexiel no cubre in-house/compliance, necesita 3 verticales (despachos/in-house/compliance), jurisprudencia debe geolocalizarse.
  • Province enum (50 provincias + 2 ciudades autónomas): para geolocalización judicial de sentencias.
  • Organization type enum (`despacho` | `in_house` | `compliance` | `mixto`): diferencia el tipo de organización para experiencia personalizada.
  • Province en legal_sources: campo para geolocalización de sentencias. Backfill script (242 TSJ Cat actualizadas vía ECLI).
  • Default province en organizations: permite ponderar jurisprudencia cercana en RAG.
  • RAG province boost: 30% de incremento en score para sentencias de la misma provincia que la organización. Se acumula con el foral boost.
  • Pipeline automático de feedback (`POST /v1/cron/analyze-feedback`): cron que procesa `product_feedback` sin analizar. Claude extrae insights clasificados (gap, feature_request, market_insight, ux_issue, validation, competitive_intel), genera embeddings, y almacena todo sin supervisión manual.
  • +1 more
Añadido2026-03-31: social proof stats, M1 video script, avatar+TTS research)
  • Social proof stats: live strip en `/academia` landing con registrations, certificates, courses, modules. Fetch SSR desde `/v1/public/academy/stats` (revalidate 1h).
  • Public stats endpoint (`GET /v1/public/academy/stats`): registrations, certificates, courses, modules counts.
  • M1 video script: guion completo para Adrián (~7 min, 4.622 chars ES + 2.584 EN). 5 bloques: LLMs, alucinaciones, caso Mata v. Avianca, protocolo verificación, flujo de trabajo. Guardado en `script_es`/`script_en`.
  • Avatar+TTS research: análisis de HeyGen (avatar) + ElevenLabs v3 (TTS con audio tags) como pipeline recomendado. Documentado en memory.
Añadido2026-03-31: Track Ciudadano en landing, Schema.org, PDF test OK)
  • Track Ciudadano en landing: nueva sección en `/academia` con las 4 áreas de derechos (laboral, inquilino, consumidor, familia), tema emerald, CTA al curso ciudadano.
  • Schema.org Course: JSON-LD del Track Ciudadano añadido a la landing de academia (4 CourseInstance con duración).
  • PDF test: verificado que `generateCertificatePdf()` genera correctamente (2.893 bytes).
Añadido2026-03-31: Track Ciudadano, PDF certificado, outreach colegios)
  • Track Ciudadano — nuevo curso "Tus derechos básicos" (audience: citizen, certificación, 80% para aprobar). 4 módulos con contenido completo ES+EN y 20 preguntas de quiz: - M1: Derechos laborales básicos (despido, indemnización, ERTE, plazos para reclamar) - M2: Derechos como inquilino (LAU, fianza, desahucio, reparaciones, preaviso) - M3: Derechos del consumidor (garantías 2 años, desistimiento 14 días, OMIC, cláusulas abusivas) - M4: Familia y herencias (divorcio, custodia, pensión alimenticia, legítimas, testamento)
  • PDF de certificado: `certificate-pdf.ts` genera PDF A4 landscape con PDFKit (tema indigo oscuro, acentos dorados, nombre, score, URL de verificación, branding Lexiel). Auto-generado al emitir certificado, subido a R2, pdfUrl guardado en BD. Botón "Descargar PDF" en página de verificación.
  • Outreach a colegios (`GET /v1/admin/academy/outreach`): 8 colegios target (Valencia, Málaga, Sevilla, Bilbao, Zaragoza, Alicante, Granada, Valladolid) con email personalizado incluyendo stats de la academia. `OutreachSection` en admin con vista expandible, copy-to-clipboard, botón mailto.
  • Sitemap: 5 nuevas URLs del Track Ciudadano × 2 locales.
Añadido2026-03-31: academy completion nudge cron, X/Twitter share)
  • Academy completion nudge cron (`POST /v1/cron/academy-completion-nudge`): email semanal a guests que empezaron la certificación pero no la completaron (ventana 3-10 días). Email con checklist de progreso (✓/○ por módulo) + CTA "Continuar la certificación". Respeta suppression y unsubscribe headers.
  • X/Twitter share: botón "Compartir en X" en la página de verificación de certificado.
Añadido2026-03-31: progress bar, academy analytics, per-module score tracking)
  • CourseProgress: barra de progreso visual en la página del curso — lee scores de localStorage, muestra checkmarks por módulo + badge de certificado.
  • Per-module score tracking: el quiz guarda el score por `moduleSlug` en `lexiel-academy-progress` (localStorage) después de cada submit — permite tracking cross-page sin auth.
  • Academy analytics API (`GET /v1/admin/academy/analytics`): registros, certificados, puntuación media, tasa de completación por módulo.
  • Analytics strip en admin: 3 KPIs + barras de progreso por módulo con porcentaje de completación.
Corrección2026-03-31: email persistence, OG cert image, dynamic breadcrumb)
  • Email persistence across modules: guest email+name now stored in localStorage after registration gate — survives module navigation and page refreshes. Certificate number also cached.
  • OG image for certificates: dynamic `opengraph-image.tsx` fetches cert data at edge and renders recipient name, score, cert number with gold Lexiel branding. LinkedIn shares now show rich preview.
  • Dynamic breadcrumb: module page breadcrumb uses API course title instead of hardcoded "Certificación IA Legal". Future-proofs for additional courses.
Añadido2026-03-31: Email certificado, LinkedIn share, registration gate, EN expansion)
  • Email de certificado: `sendCertificateEmail()` — badge dorado, número de cert, enlace de verificación. Enviado automáticamente desde `tryIssueCertificate()` (fire-and-forget).
  • LinkedIn share: botón "Añadir a LinkedIn" en página de verificación de certificado usando URL oficial de LinkedIn profile/add certification.
  • Registration gate: el quiz pide nombre+email antes del primer submit (necesario para progreso + certificado). Opción "Sin registro" disponible.
  • Banner de certificado: cuando la auto-certificación emite un cert, el quiz muestra banner dorado con número y enlace.
  • Contenido EN expandido: módulos 2-4 ampliados a paridad con ES (~6K chars cada uno vs ~3K previos).
  • Proxy /api/academy/register: ruta API en apps/web para registro desde marketing site.
Añadido2026-03-31: Admin CRUD, auto-certificación, contenido educativo completo)
  • Admin CRUD academia: 14 endpoints en `/v1/admin/academy/` (courses CRUD, modules CRUD, quiz questions CRUD, certificates list). Panel visual `AcademySection` en admin dashboard con árbol colapsable curso → módulo → preguntas, toggle publish, vista de certificados emitidos.
  • Certificación automática: `tryIssueCertificate()` — cuando un usuario completa todos los módulos de un curso de certificación con ≥ passing score, se emite un certificado automáticamente (número LEX-CERT-YYYY-XXXXX). Funciona tanto para usuarios autenticados como guests con email.
  • `generateStaticParams`: pre-renderizado de cursos y módulos en build time (ambas locales).
  • Contenido educativo completo: ~33K caracteres ES + ~19K EN para los 5 módulos de Certificación IA Legal: - M1: LLMs, alucinaciones, verificación de fuentes, caso Mata v. Avianca - M2: RGPD, técnicas de anonimización, modelos de infraestructura, caso práctico filtración - M3: 5 recomendaciones CGAE con ejemplos prácticos y cláusula modelo para hoja de encargo - M4: 6 casos de uso reales (investigación, redacción, contratos, plazos, atención al cliente, vista contraria) - M5: Clasificación AI Act, sesgo algorítmico, futuro de la profesión, código ético de 10 principios
Añadido2026-03-31: Academia pública completa + analytics firma + corpus health + email templates)
  • Academia pública — curso reader (`/academia/cursos/[slug]/[moduleSlug]`): módulo SSR con vídeo player (fallback "próximamente"), contenido Markdown con `react-markdown` + `remark-gfm`, quiz interactivo (`ModuleQuiz` client) con multi-choice/multi-select/true-false + score local fallback, navegación prev/next módulo.
  • Academia pública — overview del curso (`/academia/cursos/[slug]`): hero con stats (módulos, minutos, passing score), lista de módulos con links, sección de beneficios, CTA "Empezar ahora" al primer módulo.
  • Academia pública — verificación de certificado (`/academia/certificado/[number]`): página pública `cache: no-store` que verifica autenticidad de certificado — muestra recipient, score, fecha, número de cert.
  • API proxy quiz (`/api/academy/quiz`): Next.js API route en `apps/web` que proxea POST de quiz al backend API (evita CORS desde SSR).
  • CTA certificación en `/academia` landing: botón "Empezar la certificación" dentro de la sección de beneficios + hero CTA corregido a URL real del curso (en lugar de anchor `#certificacion`). Schema.org url actualizada.
  • Corpus health panel (admin): detección de fuentes obsoletas, health score por país, botón de refresh por fuente. `CorpusHealth` component + `/v1/admin/corpus/health`.
  • Firm profitability analytics (`/analytics/firm`): monthly revenue, utilization rate, by-area y by-lawyer breakdown. `FirmAnalytics` page + `/v1/analytics/firm/profitability`.
  • Email templates settings: personalización de plantillas por organización (bienvenida, cierre expediente, facturación). `EmailTemplatesSettings` component + `/v1/settings/email-templates`.
Añadido2026-03-31: particular Playwright tests + notification pref TS fix + retainers UI)
  • Playwright `particular` project: `particular.setup.ts` + `particular-plan.spec.ts` — 9 tests (dashboard landing, redirect de rutas lawyer-only, sidebar, chat, analisis-contratos, boe-alerts, settings BOE prefs, deadline reminders NOT visible).
  • `dev-login` particular: `particular@lexiel.ai` → plan `particular`; botón verde en `login-form.tsx` con `data-testid="dev-login-particular"`.
  • `playwright.config.ts`: proyecto `particular` separado con `storageState: tests/.auth/particular.json`; `testIgnore` en `chromium` para evitar doble ejecución.
  • `RecurrentesView`: nuevo tab "Recurrentes" en facturas — CRUD completo de retainers (crear/editar/pausar/eliminar) con cliente, importe, descripción y día de facturación 1-28.
  • Comparador de contratos (`/tools/comparar-contratos`): nueva página side-by-side de dos versiones de contrato con análisis estructurado de diferencias, riesgos y recomendaciones.
Corrección2026-03-31: push pref audit completo
  • `case-share-public.ts` — 2 push sin guard: view notification + comment notification ahora respetan `caseActivityPush` (async pref-check fire-and-forget pattern).
  • `testimony-reminders` cron: push a creador de prep sin pref check. Pre-fetch de prefs para todos los `createdBy` + guard `caseActivityPush !== false`.
  • `jurisprudence-alerts` cron: push a usuario de saved search sin pref check. Añadido `leftJoin(notificationPreferences)` en query inicial + guard `caseActivityPush !== false`.
  • `deadline-reminders.ts` (lib): bonus push en canal email sin pref check. `sendViaChannel` recibe `deadlineRemindersPush`; guard `!== false` protege el fire-and-forget.
  • Auditoría completa: 21 llamadas a `sendPushToUser` auditadas — todas respetan el pref apropiado (`caseActivityPush`, `boeAlertsPush`, `deadlineRemindersPush`, `documentExpiryPush`, `caseFollowupPush`, `weeklyDigestEmail`).
Corrección2026-03-31: particular plan API + UX audit)
  • `caseActivityPush` en rutas transaccionales: `quotes.ts` (presupuesto aceptado/rechazado), `cases.ts` (handoff a nuevo asignado), `automation.ts` (`send_notification` a miembros) — todos sin pref guard. Añadido `leftJoin(notificationPreferences)` en cada query de usuario + guard `caseActivityPush !== false`.
  • `deadlines.ts` bloqueo global incorrecto: `lawyerOnly` en `use('*')` impedía que usuarios `particular` vieran sus plazos de prescripción auto-detectados. Migrado a per-endpoint: GET accesible para todos; POST/PATCH/DELETE/templates/bulk siguen con `lawyerOnly`.
  • `deadlines-client.tsx` redirect erróneo: redirigía a `/chat` en vez de mostrar plazos. Ahora muestra `DeadlinesView` (botones de creación ocultos para `particular` via `isParticular` en `deadlines-view.tsx`).
  • Dashboard 403 en ciudadanos: `Promise.all` inicial incluía `/v1/cases` + `/v1/analytics/overview` (bloqueados para `particular`). Añadido early return para `isParticular` que solo fetchea `/v1/deadlines/upcoming`. Elimina el error de carga en el dashboard ciudadano.
Corrección2026-03-31: notification pref TS fix)
  • `documentExpiryPush` canal faltante: cron `document-expiry-alerts` solo enviaba email; ahora envía push si `documentExpiryPush !== false`.
  • `weekly-digest` cron inconsistente: no chequeaba `weeklyDigestEmail` del admin ni enviaba push. Añadido `leftJoin(notificationPreferences)` + early return + push send.
  • TS2367 en `notifications.ts:985`: redundant `!== false` tras early return TypeScript-narrowing. Eliminado el guard duplicado.
Corrección2026-03-31: push prefs completados + particular UX hardening)
  • `boeAlertsPush` en `boe-alerts` (por expediente): push se enviaba sin comprobar el toggle. Añadido `boeAlertsPush` a `ownerPrefs` select + guard.
  • `boeAlertsPush` en `boe-digest` (por área): mismo patrón. Añadido a `orgRows` + type `OrgOwner` + guard.
  • `caseActivityPush` en `portal-message-alerts`: push a abogado sin consultar pref. Añadido leftJoin en query de `caseRow` + guard.
  • `caseActivityPush` en `budget-alert` cron: pre-fetch de prefs para todos los usuarios involucrados + guard.
  • `caseActivityPush` en `stale-case-alerts`: pre-fetch de prefs por `assignedTo` + guard.
  • `caseActivityPush` en `profitability-alerts`: pre-fetch de prefs por `assignedTo` + guard.
  • `deadlineRemindersPush` en `criminal-record-alerts`: ambas fases (cancelación + 30d previos) ahora añaden leftJoin a `notificationPreferences` + guard.
  • Settings UX — `particular` users: `boeAlertsEmail`/`Push` por canal extraídos del bloque `!isParticular` → ahora visibles y configurables por ciudadanos.
  • +1 more
Corrección2026-03-31: notification prefs wiring + cron hardening)
  • Crons `case-followup-reminder` + `case-weekly-summary`: no filtraban organizaciones soft-deleted. Añadido `innerJoin(organizations, isNull(deletedAt))`.
  • Crons `task-priority-escalation` + `case-watchdog` en `automation.ts`: misma corrección de org soft-delete.
  • `notification-preferences` PATCH + dismiss: patrón check-then-insert con race condition. Convertido a `onConflictDoUpdate` atómico.
  • BOE alerts (abogados + ciudadanos): no respetaban `boeAlertsEmail` per-channel toggle. Añadida verificación `boeAlertsEmail !== false` en los 3 crons BOE.
  • Deadline reminders cron: no pasaba `deadlineRemindersEmail`/`Push` al `sendDeadlineReminder`. Ahora filtra `channelPriority` según toggles.
  • Case followup reminder: nunca consultaba preferencias. Añadida query de prefs + check `caseFollowupEmail`/`Push`.
  • Weekly digest cron: solo checkeaba `weeklyDigestEnabled` global, no `weeklyDigestEmail`. Añadido check.
  • `dashboard.ts` TS error: `users.firstName`/`lastName` no existen en schema (solo `name`). Corregido a `users.name` + derivación de iniciales.
Añadido2026-03-31: notification prefs granulares)
  • Notification preferences granulares (`migration 0021`): 10 columnas nuevas — `deadlineRemindersEmail/Push`, `documentExpiryEmail/Push`, `caseFollowupEmail/Push`, `caseActivityPush`, `weeklyDigestEmail`, `boeAlertsEmail/Push`. UI en settings.
  • Benchmark regression spec (`apps/web/tests/benchmark-regression.spec.ts`): test automatizado de benchmark.
Corrección2026-03-31: pricing + email + sidebar bugs)
  • `pricing.tsx`: `equipo` plan CTA generaba `?plan=professional` — `planToApiName['equipo']` mapeaba a `'professional'` en vez de `'team'`. Todos los clicks en "Plan Equipo" creaban suscripciones Professional (precio incorrecto, seats incorrectas). Corregido a `'team'`.
  • `trackPlanSelect` type no incluía `particular` — analytics drops silenciosos para el plan particular. Corregido en `packages/shared/src/analytics.ts`.
  • Plan particular con precios EUR hardcodeados para LATAM — el pricing component mostraba €14.90/€9.90 incluso con divisa MX/CO seleccionada. Añadidos `particularMonthly`/`particularAnnual` a `CURRENCY_BY_LOCALE` sincronizados con `start-client.tsx`.
  • Email broken links `settings/billing` → `settings/subscription` — dos CTAs en emails (trial ending + subscription changed) enlazaban a `/settings/billing` que no existe. Ruta correcta es `/settings/subscription`.
  • Sidebar `/guias` visible para plan particular — la página `/guias` llama a `/v1/compliance/overview` (contenido para despachos). Añadido `particularHidden: true`.
  • Sidebar `/tendencias` visible para plan particular — contenido de AI regulation/legaltech, irrelevante para ciudadanos. Añadido `particularHidden: true`.
  • Hero precios page excluía ciudadanos — "IA legal al alcance de todos los abogados" corregido a "IA legal al alcance de todos".
Corrección2026-03-31: firm wizard + particular plan hardening)
  • Firm setup wizard: logo upload endpoint no existía — `/v1/profile/organization/logo` nunca fue creado; la subida fallaba silenciosamente para todos los usuarios. Eliminado el campo de logo del wizard.
  • Firm setup wizard: billing profile con datos hardcoded — `addressCity`, `addressPostalCode`, `addressProvince`, `addressCcaa` hardcodeados como "Madrid" para todos los abogados independientemente de su ubicación. Paso reemplazado por vista informacional con CTA a Settings.
  • `usage-counter.tsx`: "Ampliar plan" incorrecto para `particular` — cuando el usuario particular alcanzaba el límite mensual, el comptador mostraba "Ampliar plan" (acción imposible). Ahora muestra "Reinicia en Xd" (día de reset del período).
  • `isLawyer` en `app-sidebar.tsx`: ignora downgrade a `particular` — `isLawyer = !!user?.barAssociation` mostraba items `lawyerOnly` a usuarios que tenían `barAssociation` pero plan `particular`. Corregido a `!isParticular && !!user?.barAssociation`.
  • Link settings/subscription incorrecto en `referrals-client.tsx` — usaba `settings?tab=subscription` (query param inválido). Corregido a `settings/subscription`.
  • Rules of Hooks en `my-day-client.tsx` — `return null` condicional antes de dos `useEffect`, violando React Rules of Hooks. Guard movido dentro del effect, `return null` movido tras todos los hooks.
  • UUID validation en `referrals.ts` — IDs no-UUID llegaban a Postgres causando "invalid input syntax for type uuid". Añadido `parseUuid()` en `GET /:id` y `POST /:id/rate`.
  • Banner "Plan Particular" en `upgrade-modal.tsx` para todos — banner se mostraba a abogados con planes de pago. Restringido a `user?.orgPlan === 'free'`.
Añadido2026-03-31: firm wizard + bulk ops)
  • Firm setup wizard: wizard de 4 pasos para nuevas cuentas de abogado (despacho, facturación, primer expediente, equipo). Redirect automático desde dashboard layout para `owner` sin wizard completado. Ruta `/[locale]/onboarding`.
  • `PATCH /v1/cases/bulk`: operaciones bulk estructuradas en expedientes (max 50 IDs) — cambio de estado, añadir etiqueta, archivar. Rate limit 10/min por org.
  • `/v1/organizations` route: endpoint de estado del wizard (`GET /onboarding-status`, `PUT /onboarding-status`). Protegido con `lawyerOnly`.
  • Schema: `onboardingData` + `firmWizardCompletedAt` en tabla `organizations` (migración `0018_sour_the_stranger.sql`).
Corrección2026-03-30: plan particular hardening round 5)
  • Evento huérfano en PlanGate: `plan-gate.tsx` disparaba `lexiel:upgrade` que nadie escuchaba. `UpgradeModal` solo escucha `lexiel:plan-limit`. El botón "Ver planes" no abría el modal de upgrade.
  • Upsell CTA en PlanGate para particular: añadido "¿Eres abogado? Ver planes profesionales →" como segunda opción en el bloqueo ciudadano; enlaza a `/settings/subscription`.
  • Tildes en blog posts: `part-2/5/10/11.ts` — correcciones de días, expedición, inscripción, hábiles.
Corrección2026-03-30: plan particular hardening round 4
  • docClassFilter incluye `doc.docType`: el filtro de clasificación AI en DocumentsView solo comparaba `doc.type` (campo manual), ignorando `doc.docType` (Claude Haiku). Los tipos AI nunca filtraban resultados.
  • Badge `docType` IA clickeable: el badge de clasificación AI era `<span>` inerte; ahora es `<button>` que aplica el filtro al hacer clic (consistente con el badge `doc.type`).
  • PlanGate en 8 páginas del grupo Expedientes + partner-missions: `procedures`, `workflows`, `deadlines`, `calendar`, `vistas-judiciales`, `signatures`, `declarations`, `partner-missions` — todo el grupo 2 del sidebar es `particularHidden: true` pero ninguno tenía `PlanGate` en `page.tsx`.
  • PlanGate en `writing/page.tsx`: `/writing` marcado `lawyerOnly: true` en tools-client, sin PlanGate en page. Ciudadanos podían acceder por URL.
  • Botón "Redactar escrito" oculto para `particular`: en DocumentsView el botón llevaba a `/writing` (API bloqueada) pero era visible en la UI.
  • `user` desestructurado en DocumentsView: `useAuth()` retornaba solo `logout`; ahora desestructura también `user` para acceso a `orgPlan`.
Corrección2026-03-30: security + build)
  • IDOR en case tags PATCH/DELETE: sin `organizationId` en el WHERE, cualquier user autenticado podía modificar/eliminar tags de otra org por UUID. Añadido `and(eq(caseTags.id, tagId), eq(caseTags.organizationId, authData.org))`.
  • Build 6/6 limpio: tras todos los cambios de sesión.
Añadido2026-03-30: plan particular hardening + features)
  • PlanGate en 7 páginas sin protección URL: `my-day`, `kb`, `agenda`, `client-comms`, `portal-messages`, `intake`, `document-requests` — todas `particularHidden` en sidebar pero sin `PlanGate` en `page.tsx`. Ciudadanos podían acceder por URL directa.
  • `partner-missions` ahora `particularHidden`: página de misiones del programa de partners era visible en sidebar para ciudadanos.
  • Plan type casts — 3 rutas con 'particular' excluido: `cron/billing.ts` (paid-first provisioning), `auth.ts` (resend activation), `cron/maintenance.ts` (Keycloak retry). Un ciudadano que hacía checkout paid-first recibía el welcome email sin el plan correcto.
  • `doc_type` en documentos: campo `varchar(50)` + Claude Haiku classifier fire-and-forget tras upload/OCR. 14 categorías legales. Migración `0015_quiet_raza.sql` aplicada.
  • Case Tags (subagent): `case_tags` + `case_tag_assignments` tablas (migración `0014`), API CRUD completa, UI en CasesListView con chips de filtro + modal de gestión, URL sync del filtro.
  • Subscription renewal reminder: cron `POST /v1/cron/subscription-renewal-reminder` (09:00 UTC), ventana 7-8 días, dedup, registrado en `railway.toml`.
  • React 19 ESLint false positives: `react-hooks/set-state-in-effect` en `animated-input-border.tsx` y `chat-gratis-client.tsx`; `useRef(Date.now())` impuro → `useRef(0)`. Build 6/6 limpio.
Corrección2026-03-29: plan particular hardening round 3)
  • Chat PageIntro contextual: `chat-client.tsx:612` muestra "Tu copiloto jurídico... redacta documentos" a todos — ahora condicional: ciudadanos ven "Tu asistente legal personal... Respuestas verificadas en el BOE."
  • PlanGate en meetings, jurisprudencia, presupuestos: sidebar ocultaba estas rutas para `particular` pero URL directa mostraba UI rota. Añadido `PlanGate blocked={['particular']}` en los 3 `page.tsx`.
  • Email copy neutro en transaccionales: `sendPaymentFailedEmail` y `sendSubscriptionChangedEmail` usaban `'abogado/a'` como fallback de nombre → `'usuario/a'`. `sendUsageThresholdEmail` ya discrimina: "Tu despacho" vs "Tu cuenta" según `plan === 'particular'`.
  • Weekly digest excluye plan particular: query en `notifications.ts` filtrada por `ne(organizations.plan, 'particular')` — ciudadanos con rol `owner` no reciben el digest de abogados.
  • Loading skeletons para 3 rutas sin skeleton: `invoices/[id]/`, `invoices/[id]/pdf/`, `derivaciones/[id]/` ahora tienen `loading.tsx` con skeleton de 3-4 secciones.
Añadido2026-03-29: mejora continua loop
  • SEO `dateModified` en blog: Article JSON-LD usaba `post.date` para `dateModified` siempre → ahora `post.updatedAt ?? post.date`. Añadido `updatedAt?: string` a tipo `BlogPost`. Imagen del Article ahora usa URL absoluta con `heroFileName` como fallback.
  • `Service` JSON-LD en `/para/*`: nuevo `layout.tsx` en `/para/` inyecta schema `Service` genérico (Lexiel como LegalInformationService con `offers` 9,90 €/mes) en las 24+ páginas de segmento — un solo punto de mantenimiento.
  • `WebApplication` JSON-LD en 7 herramientas: `pension-viudedad`, `incapacidad-permanente`, `erte`, `modelo-303`, `recargo-prestaciones`, `tasa-judicial`, `test-insolvencia` — eran las únicas calculadoras sin schema `WebApplication/SoftwareApplication`.
  • `getBulkMonthlyUsage` (elimina N+1): nueva función en `usage.ts` que pre-agrega usage de N orgs en 1 sola query `GROUP BY organizationId, type`. `usage-threshold-alerts` cron pasa de N queries a 1 — también filtra `unlimited` orgs antes del `processInBatches` y sube el batch size de 10 a 20.
  • Rate-limit `GET /v1/public/freemium/status`: 60 req/min por IP — antes sin límite, podía usarse para sondear Redis.
  • `case_share_tokens`: migración `0010` + `case-share-public.ts` endpoint `/v1/public/share/:token` + `POST /v1/cases/:id/share` + `DELETE /v1/cases/:id/share`. Comparte expediente con cliente vía URL con token 32-byte + TTL 30 días.
  • `cleanup-expired-share-tokens` cron: borra tokens expirados (diario 03:30 UTC) — registrado en `railway.toml`.
  • `lawyer-weekly-digest` cron: digest de productividad individual por abogado (lunes 09:00 UTC) — registrado en `railway.toml`. Contenido: casos activos, horas semana, alertas BOE últimos 7d, legal tip rotativo (8 consejos). Opt-out via `notificationPreferences.weeklyDigestEnabled`.
  • +4 more
Corrección2026-03-29: plan particular hardening
  • Freemium `/status` endpoint: `GET /v1/public/freemium/status` expone `{ max, questionsLeft }` leyendo `MAX_FREE_QUESTIONS` desde env var. El frontend ya no tiene la constante `MAX_QUESTIONS=5` hardcodeada — se fetcha en mount y el contador/paleta de límite refleja el valor real del servidor sin redeploy.
  • Race condition quota freemium: patrón read-check-incr reemplazado por incr-check-decrement (atómico). Dos requests concurrentes ya no pueden superar el límite.
  • Mensaje de límite dinámico: "5 preguntas gratuitas" hardcodeado → `${MAX_FREE_QUESTIONS}` interpolado. Si se cambia la env var a 3 ó 10, el mensaje es coherente.
  • `usage.ts` fallback a `free`: dos lugares en `checkAndRecordUsage` y `checkDocumentUploadLimit` que hacían fallback a `PLAN_LIMITS.professional` para plan desconocido → ahora `PLAN_LIMITS.free` (safe default).
  • Org name para particular: webhook Stripe ya no crea `"Despacho de X"` para ciudadanos — usa solo el primer nombre.
  • Onboarding "Completar más tarde": el botón de skip ahora redirige a `/chat` para particular en vez de dejar al usuario en `my-day` (flash con contenido de abogado).
  • Cron retry nurture emails: `maintenance.ts` ahora hace LEFT JOIN con organizations para obtener el plan y despacha `sendCitizenTrialNurtureEmail` vs `sendTrialNurtureEmail` según corresponda.
  • `welcomeHtml` plan-aware: ciudadanos ya no reciben bienvenida con "abogado/a" ni lista de features de Compositor Legal — mail adaptado con features reales del plan Particular.
  • +5 more
Añadido2026-03-29: mejora continua loop)
  • OG images en 7 páginas ciudadanas: divorciados, consumidores, herederos, trabajadores, inquilinos, propietarios-alquiler, autónomos — todas sin opengraph-image.tsx (preview genérica en redes sociales). Ahora generan preview contextual via `generateSpecialtyOG`.
  • OG image en /para hub: el hub de segmentos (`/es/para/`) tenía openGraph sin `images:`. Añadida imagen dinámica via `/api/og/para`.
  • FAQPage schema en /para/particulares: 6 Q&A (ES+EN) para Google rich snippets.
  • Citizen crosslinks en 7 specialty pages: penal, fiscal, mercantil, extranjería, bancario, sanitario, tecnológico.
  • Email inmediato de plazo prescriptivo: cuando IA detecta plazo `urgent`/`upcoming` en conversación particular, se envía email vía Resend al instante.
  • `trackFreemiumCtaClick` en muro de pago: estaba importada pero nunca llamada — ahora ambos botones (anual/mensual) disparan el evento específico del funnel freemium.
  • plan-gate redirect particular: redirigía a `/[locale]` genérico → ahora redirige a `/[locale]/chat` (su workspace real).
  • Índice compuesto `ai_ratings_target_user_idx`: `(target_type, target_id, user_id)` — eliminado full table scan en el JOIN messages↔ai_ratings en cada load del chat. Migración `0009_real_khan.sql` aplicada.
  • +1 more
Añadido2026-03-28: particular funnel completion)
  • FAQPage schema en `/para/particulares`: 6 preguntas Q&A (ES+EN) añadidas al JSON-LD para rich snippets en Google. Cubre: "¿es Lexiel un abogado?", precio, cancelación, tipos de dudas, verificación legal.
  • Citizen crosslinks en 7 páginas de especialidad: banners emerald en derecho-penal (→/particulares), derecho-fiscal (→/autonomos-y-freelancers), derecho-mercantil (→/autonomos-y-freelancers), derecho-extranjeria (→/particulares), derecho-bancario (→/propietarios-alquiler), derecho-sanitario (→/particulares), derecho-tecnologico (→/particulares).
  • Email inmediato de alerta de plazo: cuando el AI detecta un plazo legal `urgent` (≤3d) o `upcoming` (≤7d) en conversación de plan particular, se envía email transaccional vía Resend al instante (fire-and-forget, no bloquea el chat stream).
Añadido2026-03-28: particular UX hardening loop)
  • `/para` hub page: nueva página índice `/es/para/` (antes 404) con 8 segmentos ciudadanos + 10 segmentos profesionales. Añadida al sitemap (priority 0.85).
  • Chat-gratis CTA en 6 páginas ciudadanas: CTAs secundarios "Prueba gratis sin registro →" en divorciados, consumidores, herederos, trabajadores, propietarios-alquiler, autónomos-y-freelancers.
  • Citizen crosslink banners en 4 páginas de especialidad: banners emerald en derecho-laboral, derecho-familia, derecho-civil e derecho-inmobiliario redirigen ciudadanos SEO a sus páginas de segmento específicas.
  • CitizenBoeAlertForm en chat-gratis: captura de email como alternativa de menor fricción cuando el usuario llega al límite de 3 preguntas gratuitas.
  • Plan Particular en upgrade modal: banner emerald "¿Ciudadano sin despacho?" antes del grid de planes de abogado — evita que ciudadanos caigan en planes de 69€+.
Corrección
  • `team-view.tsx` guard: usuarios del plan particular redirigidos a `/` al intentar acceder a `/team` directamente (URL directa, no sidebar).
  • `comparativa/page.tsx`: precio inicial de Lexiel corregido de "69€/mes" a "desde 9,90€/mes".
  • `onboarding-overlay.tsx` i18n: plan 'particular' mostraba 'Personal' en inglés (inconsistente). Extraído `PLAN_LABELS_EN` — ahora muestra 'Citizen'.
  • Pricing JSON-LD: Plan Particular (9,90€) no aparecía en el schema.org SoftwareApplication offers de `/precios`.
Añadido2026-03-28: mejora continua loop
  • Normalización 14 días de prueba: ~110 strings en 65 archivos (LATAM, derecho-*, para/*, FAQs, componentes, OG images, API) mostraban "7 días" mientras el trust bar de /precios ya decía "14 días". Limpieza completa de la inconsistencia.
  • Panel de Feature Flags (`/admin/feature-flags`): tabla de solo lectura que muestra todos los flags con estado, planes, roles y grupo sidebar. Útil para auditoría.
  • llms.txt actualizado: añadidos segmentos consumidores, autónomos, jurídico-empresa, notarios, mediadores + nueva sección de integraciones B2B widget.
  • `navbar-timer` idle state: green dot + `00:00` siempre visible cuando no hay cronómetro activo (Clio-inspired affordance, mejor discoverability).
  • `SearchTrigger` acepta `className` override: permite que el sidebar indigo use colores propios sin duplicar el componente.
  • Citizen profile en seed-demo-data: org citizen + 1 user demo + 1 case monitorio vecinos (c12) + 6 facturas adicionales para demo solo.
Corrección
  • Colombia score benchmark: tabla de países mostraba 90% pero score RAG-hybrid confirmado es 96% (consistente con twitter meta y llms.txt). Actualizado `benchmark-page.tsx`.
  • Aria-labels en botones icono: `clauses-library-view`, `meetings-view`, `client-comms-view`, `calendar-view` tenían botones X/eliminar sin aria-label.
  • OG metadata en /contacto y /changelog: ambas páginas carecían de `openGraph` en `generateMetadata`.
  • Sitemap: añadidas 5 páginas ausentes (`/academia`, `/latam/brasil`, `/latam/cuba`, `/latam/portugal`, `/latam/republica-dominicana`).
  • `start-client.tsx` y `onboarding-overlay`: mostraban "7 días" en UI después de la normalización a 14 días.
  • API `demo.ts` y `notifications.ts`: mostraban "7 días" en contexto LLM y emails de la secuencia de prueba.
Añadido2026-03-28: sidebar redesign + widget hardening + VS dual CTA)
  • App sidebar indigo gradient: `from-[#312e81] to-[#1e1b4b]` (Harvey/Linear-inspired dark navigation). Logo → `logo-white.svg`. All text/icon colors updated: active = `bg-white/15 text-white`, hover = `hover:bg-white/[0.08] hover:text-white`. Dead CSS variables removed from `globals.css`.
  • Widget `allowedOrigins` CORS enforcement: `isOriginAllowed()` helper validates `Origin` header against DB field. Returns 403 for unauthorized origins, reflects exact origin in `Access-Control-Allow-Origin` with `Vary: Origin`. Origin validation placed after auth, before quota.
  • Widget IP rate limiting: `rateLimit('widget-ip', { max: 30, windowMs: 60_000 })` on `POST /v1/public/widget/chat`. Prevents abuse from single IPs before auth.
  • Widget analytics UI: `GET /v1/api-keys/:id/widget-usage` endpoint. API keys page fetches daily usage in parallel (`Promise.allSettled`). Progress bar with color thresholds (teal ≤75%, amber ≤90%, red >90%).
  • Widget key type UI: form pre-selects type from `?type=widget` URL param. Teal "widget" badge on key cards. One-time embed snippet display when new widget key is created.
  • `PageIntro` onboarding hints: dismissable amber card (localStorage) added to API Keys, Webhooks, and VeriFactu pages — explains feature purpose on first visit.
  • Suspense boundary on `/settings/api-keys/page.tsx` for `useSearchParams` compatibility with Next.js app router.
  • Dual CTA on all 6 VS competitor pages: smartlaw, legalitas, tirant, aranzadi, harvey, lefebvre. Primary: "Empezar — 7 días gratis", secondary ghost: "3 preguntas gratis sin registro" → `/chat-gratis`. Removes friction for undecided visitors.
  • +2 more
Corrección
  • `logo-sidebar.svg` missing: Non-existent file reference in sidebar redesign. Fixed → `logo-white.svg`.
  • Dead CSS vars in `globals.css`: 7 unused `--sidebar-bg-*` variables removed (10 lines dead code).
  • TypeScript incremental cache false positives: `PageIntro` import errors on different files each run. Root cause: stale `.tsbuildinfo`. Fixed with clean `rm -f tsconfig.tsbuildinfo`.
Añadido2026-03-28: widget B2B embebible + /integraciones widget section)
  • Embeddable B2B widget (`apps/web/public/embed/widget.js`): 249-line vanilla JS widget, Shadow DOM aislado, zero innerHTML con datos de usuario (XSS-safe). SSE streaming idéntico a `/chat-gratis`. Features: chat bubble, panel expandible, historial de mensajes, citation tags, fade-in al cargar.
  • Widget API key type: columnas `keyType` y `allowedOrigins` añadidas a `api_keys` schema. Migración 0007 aplicada. Prefijo `lx_wgt_xxx` para validación rápida en `validateWidgetKey()`.
  • `POST /v1/public/widget/chat`: auth por `X-Widget-Key` o `?key=`, cuota 200 preguntas/org/24h en Redis (`widget:org:{orgId}:{date}`), CORS abierto (`Access-Control-Allow-Origin: *`), RAG + citation verification.
  • `/integraciones`: nueva tarjeta de widget (paleta teal, MessageSquare icon) + sección de código embed syntax-highlighted. CTA → `/settings/api-keys?type=widget`.
  • Sitemap: `/chat-gratis` añadido con priority 0.95 / changeFrequency weekly.
  • cláusulas-suelo: CTA mejorado — dual botón (consultar + guía propietarios).
Añadido2026-03-28: freemium chat + BOE citizen alerts + nav discoverability)
  • Freemium chat `/chat-gratis`: 3 preguntas legales sin registro — IP-gated via Redis (24h window), SSE streaming, verified citations, limit-reached CTA → subscription. Entry point for top-of-funnel citizen visitors.
  • BOE citizen alerts: `boeTopics` jsonb column in `newsletter_subscribers` (migration 0006). `POST /v1/newsletter/boe-subscribe` with 6 topics (trabajo/alquiler/herencias/divorcio/autonomos/consumidor) + double opt-in email. `POST /v1/cron/citizen-boe-digest` weekly cron (Mondays 08:15 UTC) with personalised BOE RSS filtering per topic.
  • `CitizenBoeAlertForm` component — topic chip selector (emerald active state) + email input. Added to 6 /para/ pages: particulares, trabajadores, inquilinos, herederos, divorciados, autonomos-y-freelancers.
  • Navbar citizen section: "Soluciones" dropdown split into "Para profesionales" + "Para ciudadanos" (6 citizen links). Right-side navbar CTA: "3 preguntas gratis" → `/chat-gratis`.
  • `/para/autonomos-y-freelancers`: New citizen landing page (~40K/mo searches). 6 problem cards, 4 FAQs, 5 tool links, OG images, BOE alerts section.
  • OG images for /para/ pages: `apps/web/app/api/og/para/route.tsx` edge runtime endpoint. All 7 citizen pages now have social OG images (1200×630, emerald theme).
  • BreadcrumbList JSON-LD: All 38 herramienta calculator pages now have BreadcrumbList schema (Google rich results eligible).
  • Freemium-first CTAs: `particulares-section.tsx` primary CTA → `/chat-gratis` (secondary → subscription). `herramientas/page.tsx` bottom CTA → dual: chat-gratis + subscription.
Añadido2026-03-28: herramienta CTA mass conversion
  • 41 herramientas adicionales con CTA: 26 citizen (laborales+fiscales+civiles), 7 split (profesionales mixtos), 8 lawyer-only. Total: 65/65 herramientas tienen CTA. Cero páginas sin conversión.
  • Citizen: despido-colectivo, erte, reducciones-jornada, IMV, baja médica/maternidad/paternidad, fogasa, irpf-anual, ganancia-patrimonial, ITP, IBI, plazos, prescripción, intereses, indemnización, RGPD
  • Split: honorarios, honorarios-abogado, costas, costes-proceso, tasa-judicial, arancel-procurador, aranceles-notariales
  • Lawyer: ai-act, calendario-fiscal, comparador-fiscal, modelos 130/303, roi, seg-social-empresa, test-insolvencia
Añadido2026-03-28: CU 90% ✅, NI +Q396 fix, benchmark audit)
  • CU 90.0% (135/150): Cuba cruzó el umbral ≥90% con Claude Sonnet RAG. Was 89.3% with Gemini 2.5-flash. 15 failures residuales (Laboral 6, Penal/Proc.Penal 3, Mercantil 2, Admin/Proc.Civil/Internacional/Laboral). CR+CU now both ≥90%.
  • NI Q396 answer key fix: CT Art. 257 establece prescripción laboral en 1 año (opción A), no 3 años. Corrected from C→A. Third NI correction after Q400 (nocturna) and Q448 (PPL). Adjusted NI score to 84.7% (127/150 corrected).
  • NI answer key audit (CT Art. 45/51/52/257): Verified Q364 (7h nocturna ✓), Q393 (1mes/año B ✓ best approx of tiered structure), Q395 (inamovilidad C=1año — CF Art.21 supports 18yo capacity but CT inamovilidad unclear), Q411 (CF Art.21 establishes 18yo capacity, C defensible).
Corrección
  • NI Q396 wrong answer key: CT Ley 185 Art. 257 = "prescribirán en un año" — not 3 years. Art. 258 extends to 2 years only for occupational accidents (not general labor actions).
  • BR benchmark duplicate IDs: `benchmark-brazil-oab-xl.ts` had IDs 1-80 colliding with `benchmark-brazil-oab.ts`. Renumbered XL to 81-160. Score unchanged (88.1%), failure report now clean with unique IDs.
Añadido2026-03-28: CR 90%, NI RAG, BR post-súmulas)
  • CR 90.0% (135/150): Costa Rica cruzó el umbral ≥90% con Claude Sonnet RAG. Was 88.7% with Gemini RAG-hybrid. Laboral 83.3% (was 66.7%), Civil 100%, Penal 95.8%. Laboral gap was model knowledge, not corpus.
  • NI RAG 82.7% (124/150): Claude Sonnet RAG +1.4pp vs no-RAG 81.3%. Laboral 72.7% (+4.5pp), Constitucional 95%, Comercial 94.4%. Still needs Gemini 3.1-pro clean run (86.7% at 75Q).
  • BR post-súmulas 87.5% (140/160): +3.7pp improvement from 83.8% pre-súmulas. 68 súmulas STJ/STF working. Admin/Proc.Penal/Tributário/Ambiental/Internacional/ECA/Filosofía 100%. Weak: Penal 70.6%, OAB Ética 81.2%, Trabalho 81.0%. Run with gemini-2.5-flash (3.1-pro rate-limited).
Añadido2026-03-28: NI 150Q final, parser fix)
  • NI 150Q benchmark: 81.3% (122/150) with Claude Sonnet no-RAG. First clean 150Q run — 0 `got=?`. Weak areas: Laboral 68.2%, Civil 77.3%, Procesal Penal 75%. Note: Gemini 3.1-pro rate-limited durante session (75Q baseline was 86.7%).
  • Parser bug fix (`benchmark-latam-all.ts`): Added 3-level answer extraction — (1) word-boundary letter `\b([ABCD])\b`, (2) option text fuzzy match via `text.includes()`, (3) last resort `[ABCD]` anywhere. Fixes `got=?` for short options like percentages, durations, years.
Corrección
  • Concurrent benchmark rate limiting: Identified root cause of `got=?` cascade — multiple concurrent Gemini API calls exhaust all 3 fallback models → silent `'?'` return. Fix: run benchmarks sequentially or use Claude for non-concurrent scenarios.
Añadido2026-03-28: no-RAG optimization HN/SV, 150Q CU/NI, BR Súmulas)
  • HN no-RAG optimization: `FORCE_NO_RAG_COUNTRIES` set in `benchmark-latam-all.ts` for GT/HN/SV/EC/BO/VE. HN: 86.7% → 92.7% (+6pp, 139/150). Verified: small-corpus countries hurt by RAG noise.
  • SV no-RAG optimization: 89.3% → 94.0% (+4.7pp, 141/150 no-RAG). Both HN and SV now ≥ 90% threshold.
  • CU + NI extended to 150Q: `benchmark-cuba-150q.ts` (Q451–Q525, 75Q: Mercantil/Penal/Laboral/Familia/Tributario/Procesal Penal) and `benchmark-nicaragua-150q.ts` (Q376–Q450, 75Q: Proc.Civil Ley 902/Laboral/Mercantil/Civil/Tributario). Both verified 0 duplicate IDs.
  • BR Súmulas STJ+STF: `ingest-brazil-sumulas.ts` ingests 38 STJ + 30 STF critical súmulas (68 total), targeting OAB exam failures in Empresarial (Súmula 233/258), Civil, Penal, Tributario.
Corrección
  • benchmark-latam-all.ts mode field: Changed `'rag'` → `'rag-hybrid'` for default runs to accurately reflect the hybrid retrieval strategy.
Añadido2026-03-28: E2E CI/CD + HN corpus quality)
  • E2E GitHub Actions workflow (`.github/workflows/e2e-app.yml`): PostgreSQL service container, dev-login auth bypass (`NODE_ENV=test`, no Keycloak), local filesystem R2 fallback, `wait-on` readiness polling, 7 test files, Playwright artifact upload on failure.
  • HN Código de Familia: Ingested from OAS PDF (893KB → 125,966 chars, 371 chunks). Fixes Q152 (edad matrimonio 18 años), Q164 (unión de hecho 2 años), Q166 (liquidación bienes).
Corrección
  • HN duplicate Código del Trabajo: Deleted non-consolidated 478K entry (`e9cc8b00`) — kept consolidado 726K. Eliminates RAG confusion from duplicate sources.
  • HN benchmark: 86.7% → 90.6% (+3.9pp, 135/149) after Familia corpus + 4 answer key corrections.
  • SV benchmark: 89.3% → 89.3% (Q296 now passes; LLM variance holds overall score).
Añadido2026-03-28: herramienta CTA mass conversion)
  • 15 herramientas ciudadanas con CTA: finiquito, desempleo, jubilacion, nomina, incapacidad-permanente, incapacidad-temporal, baremo-accidente, herencia, hipoteca, horas-extraordinarias, clausulas-suelo, actualizacion-renta, fogasa, pension-viudedad, excedencia — todos con sección CTA `?plan=particular&interval=year` emerald.
  • Split CTAs en cuota-autonomo y procedimientos — 2 cards (ciudadano 9,90€ / abogado 69€).
  • isd-sucesiones: primera sección CTA añadida (calculadora impuesto sucesiones, muy citizen-oriented).
  • `/para/propietarios-alquiler`: nueva landing page desahucio/LAU (~30K/mes "desahucio España") con 6 tarjetas, 4 FAQs JSON-LD.
Añadido2026-03-28: citizen SEO expansion)
  • `/para/herederos`: Landing page herencia/sucesiones (40K+ búsquedas/mes) — 6 tarjetas de problema, 4 FAQs con JSON-LD FAQPage, CTA emerald `?plan=particular&interval=year`.
  • `/para/divorciados`: Landing page divorcio/familia (60K+ búsquedas/mes) — 6 tarjetas (pensión compensatoria, custodia, vivienda, convenio regulador, régimen económico, modificación medidas), 4 FAQs con JSON-LD FAQPage.
  • Sitemap: 2 nuevas páginas ciudadanas (priority 0.9, changeFrequency monthly).
  • CTAs herramientas → particular: pension-alimenticia, convenio-regulador, sanciones-aeat, burofax actualizados a `?plan=particular&interval=year` con color emerald.
Corrección
  • vs/legalitas particular CTA: Faltaba `&interval=year` en el CTA del plan Particular.
Añadido2026-03-28: citizen funnel hardening)
  • 3 citizen SEO landing pages (B001 captación): `/para/inquilinos` (6 problemas fianza/alquiler/vicios/cláusulas), `/para/trabajadores` (despido/horas/mobbing/finiquito), `/para/consumidores` (garantía/cláusula abusiva/estafa/desistimiento). Todos con FAQ JSON-LD Schema.org, CTA `?plan=particular&interval=year`, cross-links desde `/para/particulares`.
  • Stripe webhook `checkout.session.expired`: Añadido a live y test webhooks via REST API — ambos con 10 eventos. Recuperación de carrito abandonado activa en producción.
  • Sitemap: 3 nuevas páginas ciudadanas (priority 0.88, changeFrequency monthly).
Corrección
  • Sidebar label 'Trabajo legal' → 'Alertas BOE' para ciudadanos: Añadido `particularLabelEs`/`particularLabelEn` al type `NavGroup` — grupo `legal` mostraba terminología de abogados a ciudadanos.
  • trial-warning cron excluía plan 'particular': Ciudadanos con 7-day trial nunca recibían email pre-cobro. Añadida ventana d5-d6 para `particular` junto a d12-d13 para planes abogado.
  • Email `trialEndingHtml`: `planLabel` 'Básico'→'Particular', `displayName` 'abogado/a'→'usuario/a', copy de continuidad correcto para ciudadanos.
  • CTAs ciudadanas en herramientas: justicia-gratuita, embargo-salario, plusvalia → `?plan=particular&interval=year` con copy "Consulta tu caso por 9,90€/mes".
Corrección2026-03-27: Academy security + billing LATAM fixes)
  • IDOR removed from Academy public router: `GET /public/academy/progress/:email` allowed anyone to enumerate any user's quiz progress by email. Endpoint removed; re-add properly when guest token system exists.
  • Academy register no-op fixed: `POST /public/academy/register` was returning success without saving to DB. Now upserts a lead record in `academyUserProgress` with name + bar association.
  • LATAM-aware abandoned checkout recovery email: Recovery email no longer shows hardcoded EUR price (e.g., "9,90€/mes") to LATAM users. Spain sees price; LATAM users see just the plan name — correct local price shown on start page after clicking CTA.
  • Academy rate limiting: Added missing rate limits to public quiz (10/min), register (5/min), and certificate verify (20/min) endpoints.
  • Academia page NewsletterSection prop: Fixed prop mismatch — component takes `locale` not `dict`.
  • env.example LATAM particular prices: All 20 LATAM particular plan price IDs (MX/CO/CL/AR/PE, test + live) added to `.env.example`; were missing despite being in production.
Añadido
  • Academy seed data: `packages/db/src/seed/academy-seed.ts` — full "Certificación IA Legal" course with 5 modules, quiz questions, and presenter scripts (M1–M5).
  • B010 E2E tests marked complete: All 4 critical test files were already committed; status updated in FEATURE_TRACKING.
Añadido2026-03-28: Plan particular SaaS improvements + revenue recovery)
  • Abandoned checkout recovery (B006): Handle `checkout.session.expired` Stripe webhook — sends recovery email with direct link back to `/start?plan=X` if user entered email but didn't pay. Typically recovers 8-15% of abandoned checkouts.
  • Marketplace deep-link fix: `/derivaciones/:id` page now redirects to `/derivaciones?ref=id` so email notification links work correctly (were 404). Card auto-expands + scrolls into view + shows brand ring border.
  • Upgrade modal citizen UX: `UpgradeModal` detects `particular` plan users and shows limit-reset date + referral CTA instead of irrelevant lawyer plan options (starter/professional/team).
  • vs/legalitas SEO citizen segment: Updated title/description/keywords to target both lawyers AND citizens. Added citizen FAQ entry, green CTA for Plan Particular (9,90€/mes), updated hero copy.
  • LATAM corpus preselection (all 20 pages): All LATAM country landing pages now pass `?corpus=XX` to `/start` links (was: all pointing to bare `/start` defaulting to Spain).
  • locale in Stripe session metadata: Added `locale` field to checkout session metadata for correct language selection in recovery/notification emails.
Añadido2026-03-28: 150Q benchmark extension + CPP UY)
  • DO 92% / GT 91.3% / HN 86.7% / SV 89.3% (150Q): All 4 countries extended from 75Q to 150Q. New questions cover Procesal, Familia, Tributario, Mercantil. Results saved to benchmark_runs.
  • UY CPP (Ley 19.293) — 420 chunks: Ingested from Parlamento.gub.uy (free alternative to IMPO subscription). UY corpus now covers Derecho Procesal Penal.
  • benchmark-latam-do-gt-hn-sv-150q.ts: 300 new legal questions (75 × DO/GT/HN/SV)
Corrección
  • Usage limit message for particular: Changed "Amplía tu plan para seguir consultando" to "Se renueva el 1 del próximo mes" — `particular` users can't upgrade to lawyer plans.
Añadido2026-03-28: Academy launch + UY 150Q + LATAM onboarding)
  • Academia landing page: Full `/academia` page (ES+EN), course catalog, certification tracking, navbar "Recursos" dropdown entry. Routes registered (`academia`/`academy`). DB schema: `academy_courses`, `academy_certificates`, `academy_modules`, `academy_lessons`, `academy_quiz_questions`, `academy_user_progress` — migration applied ✅
  • UY 92.7% (150Q RAG): Uruguay 139/150. First 150Q run for UY. CPP source gap noted (IMPO subscription required)
  • LATAM CTA corpus preselection: All 20 LATAM country pages pass `?corpus=XX` to `/start` CTA links for better onboarding (preselects correct legal corpus)
Añadido2026-03-27/28: Benchmark sweep + corpus quality)
  • MX 96% (150Q RAG-hybrid): México 144/150, 140K chunks. Q57 fixed (SCJN 11→9 reforma judicial 2024)
  • CO 96.7% (150Q RAG-hybrid): Colombia 145/150, 740K chunks — best LATAM individual score this session
  • CR 88.7% (150Q RAG-hybrid): Costa Rica improved from 86.7%/75Q. 16 source titles fixed (SCIJ→proper names), reindex 7,110 chunks, NA-option bug fixed (3-option questions showed '(opción no disponible)' as D), Q73/Q74 benchmark corrected
  • NI 86.7% (75Q RAG): Nicaragua post-fix from 80%. 5 benchmark errors corrected (Q315/318/329/330/333 — confirmed against CP/CPP corpus)
  • PE 98.7% + CU 93.3%: Confirmed post-fix (Q47 PE + Q395 CU corrected)
  • DO 96% no-RAG saved: baseline saved to benchmark_runs; no-RAG > rag-hybrid (-1.3pp — model knows RD law baseline)
  • BR source title fixes (total 109): 24 raw filenames + 15 "L-number" titles corrected to proper law names for RAG quality
Corrección
  • benchmark-latam-all NA filter: 3-option questions (Costa Rica Colegio Abogados exam format) were showing '(opción no disponible)' as option D to the model. Fixed by filtering before passing to Gemini and dynamically adjusting valid letters in prompt. CR Deontología: 75%→87.5%, Familia: 77.8%→94.4%
  • Benchmark MX Q57: SCJN ministers 11→9 after 2024 judicial reform (art. 94 CPEUM amended)
  • Benchmark CR Q73: Answer B→D — CT art. 507 defines 'conflicto jurídico' as interpretación/aplicación de norma (D), not example (B)
  • Benchmark CR Q74 (official): Answer B→C — funcionarios sin derecho a huelga → arbitraje obligatorio (CT art. 397)
  • Duplicate NI benchmark entry: Removed duplicate benchmark_runs entry from concurrent session saves
  • UY CPP bad source: Deleted source with "Acceso no válido" content (IMPO subscription required), cleaned 1 orphan embedding
Corrección
  • Auth token null-safety (Critical — Rodrigo bug): Added `api.getAuthHeaders()` method to ApiClient; replaced 20+ raw `Authorization: Bearer ${api.getToken()}` patterns across all fetch calls (documents, chat, deadlines, writing, burofax, etc.) — was sending `Bearer null` on first render before auth initialized
  • listCasesQuerySchema missing practiceArea values: `fiscal`, `propiedad_intelectual`, `internacional` now valid as filter params (were returning 400)
  • recordUsage non-blocking: Billing counter failure no longer returns 500 and orphans already-uploaded documents
  • legal-opinion-modal: Token fallback to empty string removed — uses getAuthHeaders() consistently
Añadido
  • B001 Plan Particular — Phase 1 MVP (session 23): Full citizen (non-lawyer) plan implementation: - DB: `planEnum` + `'particular'` value, Drizzle migration applied, `PLAN_LIMITS.particular` (30 queries/mo, 5 docs) - AI: `PARTICULAR_MODE_SUFFIX_ES/EN` (plain language + mandatory disclaimer), `particularMode` flag propagated through `streamChat()`, `streamMultiModelChat()`, Gemini path, and conversations route - Sidebar: `particularHidden` prop hides 20+ lawyer-only items (cases, clients, billing, calendar, team, etc.) for plan particular - Onboarding: 2-step citizen flow — situation selector (laboral/vivienda/familia/consumo/herencia/otro) → chat; skips lawyer profile form - Marketing: `/para/particulares` landing page with JSON-LD Product schema, pricing comparison (7€ vs 69€), 6 use cases, 6 benefits, social proof - Billing: `PARTICULAR_MONTHLY/YEARLY_PRICE_ID` env vars in stripe.ts; checkout route already accepted `particular`
  • Benchmarks DO+PA: República Dominicana and Panamá (150 questions), benchmark-latam-all updated
  • SEO/LLM discoverability: JSON-LD Organization+WebSite schema, llms.txt link rel, LLM crawlers opened
Añadido
  • 13 LATAM Countries Live: GT, CL, PE, EC, PY, VE, BO, SV, HN, UY activated (from 3 to 13)
  • E2E Playwright Tests: Chat, invoicing, tools, landing (35 tests, 460 lines)
  • Web Vitals → PostHog: LCP, CLS, INP, TTFB performance monitoring
  • Sentry Client Configs: Complete error tracking for both frontends with session replay
  • Expanded Corpus: UY +10 labor laws, NI +3 sources, CR +22 sources from SCIJ
Corrección
  • RAG Country-Scoped Search (Critical): Fixed 0-context bug for non-ES/MX/CO countries
  • RAG Corpus Gate: 50K → 2K threshold — 9 countries regained citation context
  • Email Templates: All 11 remaining → emailShell (Noir Refinado)
Cambio
  • Migration Squash: 97 → 1 baseline (-1.2M lines)
  • Marketing Data Sync: 20 countries, 130K sources, 2.59M chunks (zero stale claims)
  • 11 LATAM Landing Pages: Real corpus data from DB
  • Missions System: Unified onboarding + engagement checklist inspired by Stripe Setup Guide - 27 missions across 2 tracks: Lawyer (15, 4 secciones) + Partner (12, 3 secciones) - 9 badges with points gamification (225 pts per track) - Auto-detection engine with 5 JSONB condition types, hooked into 13 API handlers - Floating Stripe-style widget (expanded/collapsed/closed), stacked below Lex Assistant - Admin panel for mission management and user progress tracking - Badge unlock toast notifications with slide-up animation - i18n support (ES + EN) - Replaces previous setup-checklist.tsx component
v1.8.4
2026-03-22
Seguridad
  • demo admin privilege escalation (critical): `demo-admin@lexiel.ai` emails share the `@lexiel.ai` domain and were passing `isLexielStaff()`, granting access to all 14 admin/staff-only routes (admin panel, benchmarks, knowledge agent, pitch, newsletter, etc.) via the public `demo-login` endpoint with `plan: 'admin'`. Fixed by adding a regex guard in `isLexielStaff()` that rejects `demo-*@lexiel.ai` addresses before the domain check — applies to all 14 call sites at once.
BugSpanish Accent Corrections (Round 2, ~290 total)
  • Second pass (13 files, 247 corrections)
  • `blog-posts/part-1c.ts`: revisión, gestión, análisis, resolución, negociación, asesoría (49 lines)
  • `hero-backgrounds.ts`: Información
  • `benchmark-data.ts`: apelación, prescripción, gestión, análisis (11 lines)
  • `demo-data/profesional.ts`: revisión, gestión, presentación, documentación, resolución, liquidación, asesoría, análisis, búsqueda, negociación, reposición (151 lines)
  • `generate-onboarding-infographics.ts`: gestión, colaboración (5 lines)
  • `ingest-colombia.ts`, `ingest-mexico.ts`, `ingest-peru.ts`, `ingest-ecuador.ts`, `ingest-ecuador-playwright.ts`: legal source titles (42 lines total)
  • `knowledge-agent/sources.ts`: asesoría, gestión, información, análisis (13 lines)
  • +3 more
BugBOE type detection
  • `cron.ts` BOE ingestion: fix type detection to correctly identify `resolución` titles (the unaccented word `resolucion` was not matching accented BOE titles). The DB enum stays `'resolucion'` but the detection string now matches `resolución` as well.
v1.8.3
2026-03-22
BugSchema Drift, Accent Fixes, Edge Cases
  • demo-seed.ts schema sync (4 drift issues)
  • `billingProfiles.addressCcaa` (notNull added) — add 'madrid' default for all demo profiles
  • `cases`: remove obsolete top-level `clientId`/`court`/`legalAnalysis` columns (now in `parties` JSONB and `caseBrief` JSONB); fix enum casts for `practiceArea`/`status`/`role`
  • `invoices`: `issuerSnapshot`/`recipientSnapshot` JSON → flat columns (`issuerFiscalName`, `issuerFiscalId`, `issuerAddress`, `clientName`, `clientNif`)
  • `timeEntries`: `date` → `startTime` (timestamp column rename)
  • invoices auto-billing edge case
  • Guard `inArray(cases.id, caseIds)` in auto-invoice path when all unbilled entries have null caseId — prevents Postgres `IN ()` syntax error.
  • Spanish accent fixes (17 files, ~250 corrections)
  • +6 more
v1.8.2
2026-03-22
ValidationPhase 3: Arrays and Final Strings
  • Array bounds — All unbounded `z.array()` validators across 15+ route files now have `.max()` constraints: conversations chunkIds/sourceIds(50), invoices timeEntryIds(500)/manualItems(200)/rectify manualItems(200)/template defaultItems(50), signatures signers(10), templates defaultTaskTitles(50), rate-cards taskRates(50)/practiceAreaRates(30), deadlines notificationChannels(4)/bulk deadlines already bounded, criminal-records condiciones(20), document-requests bulk-remind requestIds(100), partner-missions evaluationResponses(20), demo sectionsVisited(50), admin-partner-testing bullets(20), feedback-tester screenshots(20), cases checklist items(100)/closure-checklist(100)/aiSuggestions(20)/docs(50)/dependsOn(20)/task-template tasks(100).
  • min-only strings — All remaining `z.string().min(1)` without `.max()` now bounded: escritos workflowId(100)/phaseName(200), case-timeline date(30)/eventKind(50)/eventId(100), demo WhatsApp from(20), procedures procedureId(100), quotes clientName(200)/token params(512), invoices rectify reason(2000)/installment dueDate(10)/pdf-zip ids(2000), pitch partner(100), portal token params(512) across 7 routes.
  • Inline constraints — invoices rectify manualItems description now bounded; clients dniNie/time-tracking hourlyRate pre-bounded before refine.
  • Result: Zero unconstrained validators remain in any API route file (strings, arrays, numbers all bounded).
PerformancePush Notification Cleanup
  • Replace N individual `DELETE` calls with single `inArray` batch delete for expired push subscriptions in both `sendPushToUser` and `sendPushToOrg` helpers.
RAGBM25 Precision Improvement
  • Remove title OR from BM25 full-text search query: content-only BM25 reduces false positives from overly broad title matches (every chunk from a source titled "Código Civil" was matching title queries regardless of content relevance).
i18nSpanish Accents Batch 2
  • Fixed missing tildes/accents in 8 web files: blog/categoria page, herramientas/aranceles-notariales, herramientas/indemnizacion, herramientas/prescripcion, para/estudiantes-derecho, para/universidades, comparador-fiscal-calculator.
v1.8.1
2026-03-22
ValidationComplete Coverage (Zero Bare Strings)
v1.8.0
2026-03-22
ValidationInput Length Constraints
  • Quote items — `itemSchema.description` now capped at 500 chars.
  • Document requests — `docType` capped at 100 chars.
  • Rate cards — `taskType`(100), `label`(200), `area`(100), `defaultHourlyRate`(20) all bounded.
  • Criminal records — Condition `descripcion`/`evidencia` capped at 500, `fechaSentencia`/`eventDate` at 10 (ISO date).
  • Cases — `notes` capped at 10,000; checklist `id`(100)/`label`(300) bounded; closure-checklist same; attachments docs `label`(300).
  • Case timeline — Bulk-pin `eventKind`(50)/`eventId`(100) bounded.
  • Clients — Onboarding `itemId` capped at 100.
  • WebAuthn — `challengeToken`, credential `id`/`type`, `clientDataJSON`/`attestationObject`/`signature` all bounded per protocol max.
  • +2 more
UXPortal Quote Action Error Feedback
  • Quote accept/reject in client portal now shows a localized error message on network/server failure instead of silently doing nothing. `quoteError` state per-quote (ES/EN).
v1.7.9
2026-03-22
BillingRefund Notification + Dashboard
  • Lawyer email on refund — `charge.refunded` webhook now fetches the invoice and org owner and sends `sendInvoiceRefundedEmail()` (amber-styled, includes invoice number, client name, amount). Lawyer is notified when Stripe processes a client refund.
  • Dashboard overdue case-health — Refunded invoices no longer count as overdue in the case health scorer (`NOT IN ('paid', 'cancelled', 'draft', 'refunded')`).
Testing
  • Vitest config — `packages/api/vitest.config.ts` created with `include: ['src//*.test.ts']` to prevent Playwright `*.spec.ts` files from being picked up during `vitest run`. Previously reported 10 fake failures (Playwright spec files run through Vitest). Now reports 2 files / 52 tests cleanly.
Code
  • TS errors resolved — Two TypeScript errors found and fixed: - `admin-partner-testing.ts:608` — logger `{ key }` shorthand in local-filesystem branch where loop variable is `file`, not `key`. - `intake.ts:335` — `hashEmail(data.email)` in `.catch()` closure lost TS narrowing from outer `if (data.email)`. Fixed with `data.email!`.
  • Broken `APP_URL` self-references — Parallel session's env-constant refactor left `const APP_URL = APP_URL` in `automation.ts:379` and `cron.ts:4752`. Removed both; file-level import from `lib/env` is used instead.
  • `hashEmail` centralized — `invoices.ts`, `unsubscribe.ts`, `newsletter.ts` each had inline `createHash('sha256')` definitions. Replaced all three with `import { hashEmail } from '../lib/crypto'`.
  • Offset guards — Applied `Math.min(Math.max(0, offset), 100_000)` to remaining 13 routes (clauses, clients, corpus, criminal-records, deadlines, document-requests, escritos, feedback-tester, knowledge, lexnet, quotes, saved-sources, dashboard).
  • `corpus.ts` DELETE rate-limit — `DELETE /v1/corpus/patterns/:id` was missing rate-limit middleware. Added `rateLimit('corpus-delete', { max: 30, windowMs: 60_000 })`.
  • `escritos.ts` APP_URL — Used centralized `APP_URL` from `lib/env` instead of `process.env.APP_URL || 'https://app.lexiel.ai'` fallback pattern.
v1.7.8
2026-03-22
SeguridadGDPR Sweep (pt. 2 & 3)
  • `hashEmail` across all API routes — Comprehensive GDPR pseudonymisation sweep covering `auth.ts`, `admin-countries.ts`, `compliance.ts`, `knowledge-agent.ts`, `partners.ts`, `partner-onboarding.ts`, `team.ts`, `case-emails.ts`, `intake.ts`. All `logger.info/warn/error` calls that previously logged raw `email` now log `emailHash: hashEmail(email)` (SHA-256, 12-char hex). No raw PII in structured logs.
  • `hashEmail` across all API libs — Same sweep for `lib/email.ts` (33 logger calls: `to: params.to` → `toHash: hashEmail(params.to)`), `lib/keycloak.ts` (8 calls across both provision functions), `lib/document-request-notifications.ts` (3 calls).
  • Pagination offset upper bound — 13 route files (`notifications.ts`, `case-finances.ts`, `clauses.ts`, `clients.ts`, `corpus.ts`, `deadlines.ts`, `escritos.ts`, `feedback-tester.ts`, `lexnet.ts`, `criminal-records.ts`, `document-requests.ts`, `saved-sources.ts`, `knowledge.ts`, `quotes.ts`) now cap `offset` at `100_000` via `Math.min(Math.max(0, offset), 100_000)`. Prevents runaway DB queries from crafted large offset values.
  • Admin feedback XSS — `GET /v1/admin/feedback` preview field applied `escapeHtml(preview).slice(0, 100)` to prevent HTML injection from user-controlled feedback content in admin panel.
  • Rate limit on GET intake form — `GET /v1/intake/:token` now has `rateLimit('intake-form', { max: 60, windowMs: 60_000 })` to prevent token enumeration.
API
  • Deterministic admin selection in `analyzeAndCreateCase` — Admin user for case assignment now uses `.orderBy(asc(users.createdAt))` to ensure consistent selection when multiple org users exist with identical timestamps.
  • Paraguay adaptive RAG similarity floor — `HIGH_NOISE_CORPUS_COUNTRIES: { py: 0.62 }` added to `rag.ts`. The bulk BACN corpus (5,423 short legislative resolutions) was causing −5pp regression vs no-RAG. Higher similarity floor reduces noise from low-quality short texts.
i18n
  • `vistas-judiciales-client.tsx` — locale-aware date formatting — `formatDate` previously hardcoded `'es-ES'` locale. Converted to accept `locale` parameter; English users now see `en-GB` formatted dates (e.g. "22 Mar 2026, 14:30" vs "22 mar 2026, 14:30").
v1.7.7
2026-03-22
Seguridad
  • GDPR — `hashEmail` sweep across 5 cron logger calls — `invoice-reminder`, `boe-case-alert`, `boe-digest`, `weekly-digest`, and `morning-briefing` all logged raw `{ email }` in `logger.warn` when skipping invalid addresses. Replaced with `{ emailHash: hashEmail(email) }` (SHA-256 truncated to 12 hex chars). PII no longer written to structured logs.
  • XSS: `case-emails.ts` — `escapeHtml` on user-controlled email body — `POST /v1/cases/:id/emails` rendered `body` (a user-supplied field) directly into an HTML `<div>` template via template literal. A crafted body could inject arbitrary HTML into email clients. Fixed: all newlines and HTML special chars escaped via `escapeHtml()` + `.replace(/\n/g, '<br>')`.
  • `calendar.ts` — `timingSafeEqual` for iCal token — iCal subscription token comparison used `===` (timing oracle). Replaced with `crypto.timingSafeEqual` on Buffer-encoded values.
  • `calendar.ts` — UUID + date pre-validation — `?caseId=` and `?member=` passed directly to Drizzle `eq()` on UUID columns without format validation. Non-UUID input triggers a Postgres cast error (400 expose). Added UUID regex guard. Also added date regex for `?from`/`?to`, and a 366-day range cap to prevent runaway DB queries.
  • Admin rate limit — Global rate limit added to `adminRouter` (`120 req/min per user`) immediately after auth middleware. Previously the 30+ admin endpoints had no shared rate limit — only some had route-level limits.
BillingRefunded Invoice Support
  • `charge.refunded` Stripe webhook — New handler in `outbound-webhooks.ts`: finds invoice by `paymentIntentId`, sets status to `refunded`, inserts audit trail entry (`refund_processed`). Required adding `refunded` to the invoice status enum in Drizzle schema, Zod validators, and all analytics aggregates.
  • Analytics: exclude refunded from billed aggregates — `billed` and `totalBilled` KPIs in `/analytics` and `/billing` now filter out `refunded` invoices. Previously a refunded invoice counted as revenue.
  • Invoices bulk action: allow archiving refunded — Bulk archive action was blocked for `refunded` status. Refunded invoices can now be archived.
API
  • outbound-webhooks LIST: cap at `MAX_WEBHOOKS_PER_ORG` — `GET /v1/webhooks` `findMany` was unbounded. Added `limit: MAX_WEBHOOKS_PER_ORG` (10) so a org with thousands of subscriptions (e.g. data corruption) doesn't return unbounded results.
Corpus
  • corpus-client: jurisdiction filter — Added `jurisdictionFilter` state + `JURISDICTIONS` array (18 Spanish autonomous communities + `nacional`). Filter is threaded into both `loadPatterns` URLSearchParams and `handleSearch` URLSearchParams. New `<select>` dropdown in the filter bar between "área" and "resultado" selects.
  • corpus-client: full EN i18n — ~25 hardcoded Spanish strings replaced with locale ternaries: search placeholder, Buscar/Limpiar buttons, filter labels ("Todas las áreas" → bilingual), outcome labels in filter select, empty state messages, pattern count, pattern card section headers (Argumentos clave, Bases legales, Nota de estrategia), delete tooltip, create modal labels (Área, Rol del cliente, Resultado, Jurisdicción, Título, Resumen, cancel/save buttons). `OUTCOME_CONFIG` labels converted from `string` to `Record<string, string>` (`{es, en}`).
Type
  • `admin-prospecting.ts` `socialProfiles` type — Previously cast as `any[]`; now typed as `Array<{ platform: string; handle: string; followers: number | null }>` matching the actual query shape.
v1.7.6
2026-03-22
API
  • `GET /v1/cases/:id/profitability` — 30-day trend comparison — Endpoint now returns `last30`, `prior30`, and `marginTrend` (% change vs. prior 30 days). Adds 6 parallel DB queries restricted to date windows using `gte`/`lte` on `createdAt`. API response is backwards-compatible; all new fields are additive.
  • case-finances: import `gte`/`lte` — Added missing date-range filter imports for the profitability trend queries.
Frontend
  • Tools page: Legal Ops section searchable — Conflict checker, clause library, AI legal opinion, and citation verifier now respond to the search bar. Introduced `LEGAL_OPS_TOOLS` searchable metadata array; the section hides when no item matches the query. Previously the 4 modal-trigger tools were invisible to search.
  • `case-profitability-view.tsx` — historical comparison badge — Margin hero card now shows a `marginTrend` pill (e.g. `+12.3% vs mes anterior`) with `ArrowUpRight`/`ArrowDownRight`/`Minus` icon and green/red/neutral color coding.
Refactoring
  • `settings-view.tsx` — split 9 sections into `settings/` directory — Reduced from 3,895 → 2,426 lines (−1,469). Extracted: `GamificationSection`, `CorpusProgressSection`, `SignaturitSection`, `StylePreferencesSection` (+ `StyleChip`), `MonthlyGoalsSection`, `TaskTemplatesSection`, `LexnetSection`, `CalendarSyncSection`, `PasskeysSection`. Each file is self-contained with its own types, API calls, and icons. 9 dead icon imports pruned from the main import line.
v1.7.5
2026-03-22
API
  • Portal batch view-tracking — New `POST /v1/portal/:token/items/view-batch` replaces N individual `PATCH` calls on portal mount (1 DB round-trip instead of N). Accepts up to 50 item IDs, only updates unviewed items via `UPDATE ... WHERE inArray`.
  • Portal items: expose `reviewedAt` + `updatedAt` — Portal `GET /:token` now returns review timestamps per item, enabling clients to see lawyer review status.
  • Pitch partner slug validation — `partner` field in `POST /v1/pitch/view` is now validated against the `PARTNERS` constant; unknown slugs are silently normalised to `null` before insert.
Frontend
  • i18n: `analytics-view.tsx` — `__unassigned__` sentinel — `byLawyer` aggregations in `analytics.ts` now emit `'__unassigned__'` instead of `'Sin asignar'`; frontend translates to "Sin asignar" (ES) / "Unassigned" (EN) at both render sites (win/loss + satisfaction sections).
  • i18n: `settings-view.tsx` — TaskTemplates section — `PRACTICE_AREAS` and `PRIORITY_LABELS` were static Spanish-only arrays; converted to `{es, en}` shape and updated 3 render sites.
  • UX: portal upload item — lawyer review status — Items in `uploaded` status now show "En espera de revisión por el abogado" / "Awaiting lawyer review" with upload date, so clients aren't left guessing.
  • UX: analytics monthly trend chart — Bars are now normalised to a fixed max height (80px) and show case count label above each bar + a Won/Lost legend. Previously bars had no common scale making comparisons misleading.
v1.7.4
2026-03-22
Frontend
  • i18n: client portal (all 5 components) — Full EN locale threading for the client-facing document portal. `PortalUploadView`, `PortalUploadItem`, `PortalMessageThread`, `PortalExpired`, `PortalCompleted` had no locale awareness. Added `locale` prop to all, threaded from `portal-client.tsx`, fixed ~40 hardcoded Spanish strings including quote actions, deadline labels, status badges, GDPR footer, date/currency formatters.
  • i18n: `analytics-view.tsx` — 7 remaining hardcoded strings in the outcomes section: "Por abogado", "Sin datos", "Tendencia mensual", "Satisfacción del cliente", "valoraciones", "Por área de práctica" (satisfaction), "Últimos resultados".
  • i18n: `case-profitability-view.tsx` — Full EN support (12 strings). Component had no locale awareness at all; added `useParams`, local `fmt()` bound formatter, and EN translations for all KPI labels, cost breakdown, efficiency indicators.
  • i18n: `settings-view.tsx` UsageBar — "Límite alcanzado" and "Cerca del límite" were hardcoded in an inline sub-component that already had `useParams()` for the unlimited label.
v1.7.3
2026-03-22
Frontend
  • i18n: `devils-advocate-view.tsx` — 3 hardcoded Spanish strings (label, textarea placeholder, character count message) were ignoring the `isEs` flag that was already defined in the component. EN users saw Spanish UI. All three now use `isEs` ternaries with proper tildes/accents.
  • i18n: `deadlines-view.tsx` — 4 hardcoded Spanish strings in the deadline creation form: "Referencia" label, "N. procedimiento" placeholder, "Notas" label, and "Ej: Art. 405 LEC (20 dias habiles)" placeholder. Also fixed missing tilde in "Contestacion" placeholder. All now use `isEs` ternaries.
SEOStructured Data
  • JSON-LD: `herramientas/ai-act` — Added `WebApplication` + `FAQPage` schemas. The page already had complete FAQ data but was missing the `<Script>` tags.
  • JSON-LD: `herramientas/cese-actividad` — Added `WebApplication` schema.
  • JSON-LD: `herramientas/prestacion-maternidad` — Added `WebApplication` schema.
  • JSON-LD: `contacto` — Added `ContactPage` schema with organization email.
  • JSON-LD: `para/derecho-foral` — Added `WebPage` schema highlighting the foral law specialization.
v1.7.2
2026-03-21
Corrección
  • Background timer `.unref()` audit — `knowledge-scheduler.ts` had 20+ `setTimeout`/`setInterval` calls without `.unref()`. Without it, every pending timer keeps the Node.js process alive during Railway graceful shutdown, causing SIGKILL timeouts instead of clean exits. Also fixed `outbound-webhooks.ts` initial `setTimeout`.
  • Double-init guards for all schedulers — `startKnowledgeScheduler`, `startDocumentRequestReminders`, and `startWebhookDeliveryCleanup` had no guard against duplicate invocations. A double-call (e.g. hot-reload or accidental startup race) would spawn duplicate intervals, doubling DB load and notification delivery. All three now have module-level guard flags matching the pattern established in `startDeadlineReminders`.
  • `document-request-reminders.ts` timers missing `.unref()` — Both `setTimeout` (initial run) and `setInterval` (recurring) lacked `.unref()`.
Frontend
  • Double-submit: DPIA download button — `settings-view.tsx`: DPIA download button had no guard; rapid clicks during the fetch would trigger multiple simultaneous PDF downloads. Added `downloadingDpia` state with `disabled` + `cursor-wait` + `finally` cleanup.
  • Double-submit: CSV export buttons — `settings-view.tsx`: The 4 CSV export buttons (expedientes/facturas/clientes/tiempos) in a map loop had no loading state. Added `exportingCsvTypes: Set<string>` state — each button is independently disabled during its download with a spinner, other buttons remain active.
  • Double-submit: deadline auto-create — `documents-view.tsx`: "Create all in calendar" button in the deadline suggestions panel could be clicked multiple times before the API call completed. Fixed by optimistically closing the panel before the API call (panel disappearing is the natural guard; count captured before close so toast shows correct number).
  • i18n: print window title — `criminal-records-view.tsx`: Cancellation request print popup had hardcoded `'Solicitud de Cancelación'` for the window title. Now uses `es ? 'Solicitud de Cancelación' : 'Cancellation Request'`.
v1.7.1
2026-03-21
Seguridad
  • JWT payload runtime validation — `auth.ts` middleware cast `payload.sub/email/org/role` directly to `string` without checking. If a Keycloak realm config change omitted a claim, downstream code would receive `undefined` as a string. Added explicit runtime checks before setting auth context.
Corrección
  • `deadline-reminders.ts` duplicate interval guard — `startDeadlineReminders()` had no guard against being called twice; calling it twice would spawn two parallel cron intervals causing duplicate notifications. Added `_deadlineRemindersStarted` module-level flag. Also added `.unref()` to both `setTimeout` and `setInterval` so the process can exit gracefully during dev/testing.
  • `cron.ts` BOE owners query includes deleted-org users — `WHERE users.role = 'owner'` without org filter would include owners of deleted organizations. Added `INNER JOIN organizations ON ... AND organizations.deleted_at IS NULL`.
  • `billing.ts`/`profile.ts`/`webhooks.ts` non-destructured singleton queries — Five routes used `const org = await db.select()...` (full array) then accessed as `org[0]` throughout the handler. Converted to `const [org] = await db.select()...limit(1)`.
  • `usage.ts checkPlanLimit` Promise.all inner query missing `.limit(1)` — Inner DB query inside `Promise.all([...])` fetched all orgs by ID without a limit. Fixed: `const [[org], usage] = await Promise.all([db.select()...limit(1), ...])`.
v1.7.0
2026-03-21
PerformanceDB Query Hardening (complete audit)
  • ~130 missing `.limit(1)` on singleton SELECT queries — Completed a systematic audit of all 59 route files plus `lib/usage`, `lib/rag`, `lib/email`. Every `const [x] = await db.select()...` query now has `.limit(1)`, preventing the DB from materializing full result sets for lookup-by-ID. Affected: `cases`, `escritos`, `clients`, `team`, `quotes`, `templates`, `clauses`, `deadlines`, `signatures`, `meetings`, `analytics`, `auth`, `billing-profiles`, `case-ai`, `case-emails`, `criminal-records`, `cron`, `document-requests`, `email-templates`, `knowledge-agent`, `notifications`, `outbound-webhooks`, `plugin`, `testimony-prep`, `workflows`, `compliance`, `documents`, `invoices`, and 3 lib files.
  • N+1 fix in bulk invoice ZIP — `POST /v1/invoices/bulk-export-zip` fetched line items per invoice in a loop. Replaced with a single bulk `inArray` query grouped in memory, eliminating N DB round-trips.
Corrección
  • UY/BO billing currency — Stripe doesn't support UYU or BOB for card subscriptions. Switched both countries to USD billing with PPP-adjusted prices (UY: $29–$43/mo, BO: ~$26–$38/mo).
  • Uruguay corpus — `corpusReady: true` (corpus is live and searchable).
  • Brazil corpus — `corpusReady: false` — product decision pending on Portuguese-only market entry.
  • Ecuador benchmark page — Status updated from `corpus` to `live` (benchmark verified).
Seguridad
  • `devils-advocate` history IDOR — `GET /v1/devils-advocate/history/:id` fetched the full analysis row and then checked `row.organizationId !== authData.org` in application code. This fetches a potentially foreign-org row before discarding it. Moved org scope into the SQL `WHERE` clause (`and(eq(id, id), eq(organizationId, authData.org))`) and added `.limit(1)`.
v1.6.8
2026-03-21
Performance
  • Dashboard endpoint caching — `/activity`, `/my-day`, and `/cases-attention` each fired 6+ parallel DB queries on every request with no caching. Wrapped in the existing `cached()` helper: `activity` → 30s per org, `my-day` → 30s per user+date (natural midnight expiry), `cases-attention` → 60s per org+date.
Seguridad
  • `processDocument` org isolation — Background document-processing pipeline was accepting an `_orgId` parameter it ignored; both UPDATE WHERE clauses used only `documentId`. An attacker who triggered processing on a foreign document ID could overwrite another org's document status. Fixed: parameter renamed to `orgId`, added `eq(documents.organizationId, orgId)` to both UPDATE clauses.
  • `processMeeting` org isolation — Same pattern: all 7 UPDATEs in `processMeeting` checked only `meetingId`. Added `orgId` parameter (threads through from `authData.org` at both call sites) with an org guard on all UPDATE WHERE clauses.
Corrección
  • Blob URL memory leak in `lex-assistant` — Each audio recording session created a `URL.createObjectURL(blob)` stored in state, but `setAudioResult(null)` on drawer close never called `URL.revokeObjectURL`. URLs accumulated across sessions. Fixed: functional state updater accesses previous blobUrl before clearing, then revokes it.
Corrección
  • NaN-unsafe pagination in `cases.ts` — `Number('abc')` yields `NaN`; `Math.min(NaN, 200)` passes silently and corrupts Drizzle's `.limit()`. Fixed with `parseInt + Number.isNaN` guard on the document-versions endpoint.
  • `fetchTimelineItems` missing `isNull(deletedAt)` on case brief — `caseBrief` lookup inside `fetchTimelineItems` queried `cases` without a `deletedAt IS NULL` guard. Soft-deleted cases would still return their brief. Added the guard.
Performance
  • 7 additional `.limit(1)` singleton lookups — `case-pdf.ts` (4), `cron.ts` (2: `documentRequests`, `organizations` in retry-notifications), `admin-benchmarks.ts` (rows[0] pattern).
  • 17 additional `.limit(1)` singleton lookups — `case-timeline.ts` (4), `case-ai.ts` (2), `api-keys.ts`, `testimony-prep.ts` (4), `team.ts`, `cases.ts` (5 + `autoGenerateCaseSummary`), `automation.ts`, `documents.ts`, `writing-drafts.ts` (3). Same Drizzle full-materialization pattern as 1.6.7.
v1.6.7
2026-03-21
Corrección
  • `getMonthlyUsage` fresh DB query on every AI action — Added 60s in-memory cache with immediate invalidation on `recordUsage` write. Prevents N concurrent GROUP BY aggregations under load.
  • `fetchWithRetry` HTTP 5xx not retried — Comment said "fall through to retry" but code threw immediately on 5xx on non-last attempts; fixed to continue the retry loop with correct delay.
  • Missing `.limit(1)` on single-row fetches — Added `.limit(1)` to lookup-by-ID queries in `invoices.ts` (3 routes), `quotes.ts` (3 queries), `conversations.ts`, `clients.ts`, `time-tracking.ts` (3 routes). Drizzle without `.limit(1)` materializes the full result set before JavaScript takes the first element.
  • Unbounded commissions query — `GET /v1/partners/me/commissions` fetched all rows; added `.limit(500)`.
  • Unbounded admin/list query — `GET /v1/partners/admin/list` fetched all partners; added `.limit(500)`.
  • Solicitud + calendar-sync double-submit — Criminal records buttons had no loading state; rapid clicks queued concurrent AI/API calls per record. Added per-record `Record<string, boolean>` loading state with `disabled` + spinner.
Seguridad
  • Audio upload type not validated — `POST /partner-onboarding/first-impression` accepted any file type; only size was validated. Added MIME allowlist (`audio/webm`, `audio/mp4`, `audio/ogg`, `audio/mpeg`, `audio/wav`, `audio/aac`) and now derives storage key extension from declared MIME.
Performance
  • `/admin/insights` unrate-limited LLM endpoint — Added `rateLimit('admin-insights', { max: 10, windowMs: 60_000 })` to the AI analysis route. Also bounded the input query to `.limit(100)` to prevent token overflow.
v1.6.6
2026-03-21
Corrección
  • CSV export double-submit (cases + invoices) — Export buttons had no loading state or disabled flag; rapid clicks queued multiple simultaneous fetch/download calls. Both buttons now disable and show a spinner during export.
  • Web 404 page hardcoded Spanish — Not-found page showed hardcoded ES text and `/es/` links regardless of locale. Now uses `usePathname()` to detect locale and renders fully bilingual (EN/ES) content with correct locale-prefixed quick links.
  • Footer + ROI calculator pricing URL — Hardcoded `/precios` slug used for both locales; EN users linked to the Spanish slug. Fixed to use `pageSlug('precios', locale)` which resolves to `/prices` for EN.
  • Calendar iCal: deadlines unbounded query — Public iCal feed fetched all deadlines in a ±270-day window without a limit. Added `.limit(500)`.
  • Calendar sync: silent catch blocks — Google and Outlook sync loops swallowed individual event creation errors silently. Now log `warn` per failed deadline with `deadlineId` context.
Seguridad
  • Partner stats endpoint enumerable — `GET /v1/partners/stats/:code` was public and unrated. Referral codes (`LEX-XXXXX`) have ~65K possibilities — trivially brute-forceable. Added `rateLimit('partner-stats', { max: 20, windowMs: 60_000, keyBy: 'ip' })`.
Añadido
  • First impression audio auto-transcription — Partner onboarding `/first-impression` now triggers fire-and-forget transcription (same pattern as product-feedback) and saves to `firstImpressionTranscript` column. Removes manual copy-listening step from admin review.
  • VeriFactu AEAT auto-submission — On invoice issuance, if `verifactuXml` and `verifactuHash` are present, the XML is submitted fire-and-forget to the AEAT registration endpoint.
  • Org deletion cascade completeness — `team.ts` org deletion transaction now also cleans up `billingCatalog`, `partners`, and `stylePreferences` rows.
v1.6.5
2026-03-21
Corrección
  • FeedbackTesterPanel: audio upload silent failure — When a recording upload failed (network error / server issue), the panel showed no error and the user could believe the audio was saved. Now displays a red banner with actionable message ("No se pudo subir el audio. Puedes enviar tu feedback por texto."). Error clears on next recording attempt.
  • MeetingRecorder: microphone error shows wrong text — The catch block for `getUserMedia()` failure was using `dict.types?.other` (evaluates to `'Otra'` — a meeting type label) as the error message. Now shows a proper localized message ("No se pudo acceder al micrófono. Concede el permiso e inténtalo de nuevo." / English equivalent).
  • Partner onboarding: no welcome email on registration — Partners who registered via invitation token received no confirmation email. Now `sendWelcomeEmail` is called fire-and-forget after successful registration.
  • client-portal.ts: NaN-unsafe limit param — `Number(c.req.query('limit') || 50)` — truthy non-numeric strings like `'abc'` bypassed the fallback and produced NaN. Fixed with `parseInt + Number.isNaN` guard.
Performance
  • meetings table: missing compound index — `meetings_org_case_idx` on `(organization_id, case_id)` added and migrated (0075). Queries listing meetings by case within an org now use the compound index instead of separate index scans.
v1.6.4
2026-03-21
Corrección
  • NaN-safe query params: `PATCH /product-feedback` (admin.ts) limit/offset and document search endpoints (`/search`, `/hybrid-search`, `/semantic-search`) used `Number(x || fallback)` which passes NaN when x is a non-empty invalid string. Replaced with `parseInt + Number.isNaN` guard throughout.
  • Commission rate hardcoded as 30%: Partner onboarding routes registered new partners with `commissionRate: '30'` — corrected to `'20'` matching the published partner program terms.
  • Prospecting status update wrong endpoint: Kanban drag-and-drop was sending `PATCH /v1/admin/prospecting/:id` with `{ status }`, but `updateProspectSchema` uses `.strict()` (no status field) — would always 400. Fixed to use `PATCH /:id/status`.
  • Word plugin auth errors not surfaced: Session expiry errors (`PluginAuthError`) in `search-tab`, `anonymize-tab`, and `redline-tab` were caught and swallowed — users saw generic API errors instead of being redirected to the connect screen. All tabs now propagate via `onUnauthorized` callback wired in `task-pane.tsx`.
  • Document content search silent error: `catch { /* keep empty */ }` in `documents-view.tsx` showed "no results" when search actually failed. Now shows an error toast.
  • Cases conflict-check input length unbounded: `name` and `nif` params had no length cap — could pass megabyte strings to Postgres ILIKE. Capped to 300 / 50 chars.
Corrección
  • Orphaned session-replay cleanup never ran: `cleanup-orphaned-session-replays` cron endpoint existed but was never scheduled in `knowledge-scheduler.ts`. Added weekly `setInterval`.
  • Changelog missing from Docker image: `docs/CHANGELOG.md` was not copied to the runner stage in `Dockerfile.app`. The "What's New" panel in the app was silently empty in production. Added COPY + multi-candidate path resolution.
  • Chat title generation blocking stream: LLM title generation ran before the SSE stream was closed, adding latency to every new conversation. Moved to fire-and-forget after `c.executionCtx.waitUntil()`.
  • Prospecting `any` types: All prospecting CRM components (`kanban-board`, `prospect-card`, `prospecting-client`) used `any[]` with no shared type — added `Prospect` interface with index signature; all state and callbacks now properly typed.
Performance
  • RAG over-fetch multiplier: Reduced 15× → 8× for filtered queries (jurisdiction/sourceType/country). 15× was causing 500ms+ latency spikes on filtered searches.
  • Feedback-tester detail fetch: Screenshots presigned URL resolution, audio URL, and tester DB query now execute in parallel via `Promise.all` (was sequential).
Añadido
  • Word plugin SSE retry: `readStreamWithRetry` utility automatically reconnects dropped SSE streams in the Word plugin analyze tab — 10s AbortController timeout per chunk, 3 retry attempts.
v1.6.3
2026-03-21
Añadido
  • Partner Prospecting CRM: Full pipeline management in admin dashboard — from research to signed agreement - Database schema: `partner_prospects` + `prospect_interactions` tables - 14 API endpoints (CRUD, AI actions, interactions, proposals, hub migration) - Kanban board with drag-and-drop (@dnd-kit/core) for pipeline management - Slide-over prospect detail panel with tabs + full-page prospect detail view - AI-powered prospect research via Gemini 3.1 Flash with web grounding - AI-powered outreach message generation via Gemini 3.1 Pro (DM + email) - Dynamic PDF proposal generation with adjustable commission, exclusivity, and duration terms - `PITCH_PARTNERS` env var allowlist for partner presentation URLs - 15 initial prospects seeded from research - Admin nav quick link for prospecting
Corrección
  • Pitch presentation 404 in Docker: `readFileSync` path resolution failed in Next.js standalone builds — `process.cwd()` points to `/app` in Docker vs. monorepo root in dev
  • Pitch gate returning file download instead of HTML: Production pitch gate now correctly renders HTML presentation rather than triggering a download
Cambio
  • `/admin/partnership-outreach` now redirects to `/admin/prospecting` (consolidated into CRM)
  • Partner-specific presentation URLs no longer require `?t=` token — slug acts as access control; per-partner tokens supported via `PITCH_TOKEN_*` env vars
v1.6.2
2026-03-21
Corrección
  • Prospecting Kanban status mismatch: Column statuses were Spanish (`investigado`, `contactado`...) but the API enum uses English (`researched`, `contacted`...). All Kanban columns always showed empty. Fixed to match the API enum.
  • ProspectPanel status labels: STATUS_LABELS/STYLES used stale values (`identified`, `engaged`, `partner`, `rejected`) not in the API enum. Updated to: `researched`, `responded`, `agreement`, `active_partner`, `discarded`.
  • MessageGenerator silent send failure: `handleSend` swallowed errors and `ChannelPicker` always showed "Registrado" even on network failure. Now returns a boolean; "Registrado" state only shown on confirmed success.
  • Prospecting fetch missing `credentials: 'include'`: All 7 raw `fetch()` calls in prospecting sub-components (`add-prospect-modal`, `interaction-timeline`, `message-generator` ×2, `proposal-editor` ×3) were missing `credentials: 'include'`. Admin routes require JWT cookie — these calls would all 401 in production.
  • CALENDAR_SECRET weak fallback: iCal token generation fell back to hardcoded `'lexiel-calendar-secret'` string — anyone with source access could forge valid tokens for any org. Now throws at startup in production if `CALENDAR_SECRET` env var is missing.
  • iCal feed no rate limiting: `GET /v1/calendar/ical/:orgId/:tokenFile` was registered before `use('*', rateLimit(...))` so no rate limit applied. Added inline `rateLimit('ical-feed', { max: 30, windowMs: 60_000 })` to the route directly.
  • `/health/deep` publicly accessible: Full dependency check (memory, uptime, service status) was reachable without auth. Added `auth` middleware to the route; `health-client.tsx` updated with `credentials: 'include'`.
  • Chat search error indistinguishable from no results: Search failure set `searchResults` to `[]`, showing "Sin resultados" when the API errored. Now uses a dedicated `searchError` boolean; error path shows a distinct red message instead.
  • +2 more
Añadido
  • Prospect PDF generation: `POST /v1/admin/prospecting/:id/generate-pdf` — dynamic partner agreement PDF with prospect terms (commission, exclusivity, duration, obligations, extras).
  • Outreach redirect: `/admin/partnership-outreach` now redirects to `/admin/prospecting` (consolidation).
  • Prospecting quick link: Added to admin dashboard nav strip.
  • Benchmarks filter persistence: Country and mode filters now initialize from URL search params (deep-link friendly).
  • Countries UI improvements: Better benchmark data display with BarChart2, external links for benchmark sources.
v1.5.14
2026-03-21
Corrección
  • Railway env vars: Set `DEMO_RESET_TOKEN`, `ADMIN_HMAC_KEY`, `AGENTEUNO_WEBHOOK_SECRET` — admin demo reset, HMAC operations, and AgentUno webhook verification were returning 503/failing silently.
  • Gemini key rotation consolidation: Removed dead `gemini-key-rotation.ts` (superseded by `gemini-keys.ts`). `markGeminiRateLimited()` now wired into streaming Gemini call path (was only called on non-streaming path).
Añadido
  • Session replay orphan cleanup cron: `POST /v1/cron/cleanup-orphaned-session-replays` deletes `session_replays` rows + R2 objects that have no referencing `partner_mission_assignments.session_replay_id` and are older than 7 days. Uses `listFilesByPrefix` to delete all batch files under the session prefix.
  • R2 `listFilesByPrefix`: Added to `storage.ts` — paginates via `ListObjectsV2Command` (handles >1000 objects), local dev falls back to `readdirSync`.
v1.6.0
2026-03-21
Corrección
  • NEXT_PUBLIC_CRON_SECRET removed: Was a security risk (client bundle exposure). `GET /v1/cron/corpus-progress` now accepts admin JWT as alternative to the cron secret — admin users authenticate with their own token via `api.getToken()`.
  • WebAuthn production config: Set `WEBAUTHN_RP_ID=lexiel.ai`, `WEBAUTHN_RP_NAME=Lexiel`, `WEBAUTHN_ORIGIN=https://app.lexiel.ai` in Railway (were missing, passkeys would have failed silently).
  • 100% param validation coverage: All 623+ API route params now validated with `zValidator` — UUIDs validated as UUIDs, strings as strings. Zero unvalidated `c.req.param()` calls remaining.
  • BOE reference format validation: `/boe/:id`, `/boe/:id/text`, `/boe/verify/:id` now validate against `^BOE-[A-Z]+-\d{4}-\d+$` regex, rejecting malformed references before hitting BOE API.
  • Structured error logging: Replaced `console.error` with `logger.warn` in escritos `share-with-client` fire-and-forget email. AI suggestions JSON parse failure now logs raw text excerpt.
  • partner-missions.ts error shapes: Standardized 4 `{ error: 'ONBOARDING_REQUIRED' }` → `{ error: { code, message } }` objects for frontend consistency.
  • rrweb type cleanup: `replay-viewer.tsx` typed with `Replayer` from `@rrweb/replay`, removed `any` casts.
  • WebAuthn type cleanup: `webauthn.ts` uses `RegistrationResponseJSON`/`AuthenticationResponseJSON` types from `@simplewebauthn/server` instead of `as any`.
Añadido
  • Resend email webhooks: Created webhook endpoint in Resend API (ID: `ca323bd3`), set `RESEND_WEBHOOK_SECRET` in Railway. Email bounce/complaint/delivery events now processed via `POST /webhooks/resend` with Svix signature verification.
  • Stripe team plan (Equipo): Created Equipo product (`prod_UBmqUZcqVL6k95`) in Stripe live with per-seat pricing — monthly €99/user (`price_1TDPHLLo0CCa9fhgHk9x9HQe`), yearly €79/user/mo billed annually (`price_1TDPHLLo0CCa9fhg0dd2JLja`). Set in Railway. Team plan checkout no longer throws.
  • session_replays `sr_org_idx`: Missing index on `org_id` added (migration 0064 + direct CREATE INDEX).
  • RAG A/B benchmark script (`benchmark-rag-ab.ts`): Measures Recall@5, Recall@10, MRR across different RAG configurations for retrieval quality comparison.
Corrección
  • Migration 0064 timestamp: Was lower than migration 0063 — Drizzle would silently skip it forever. Fixed `when` to `lastApplied + 100000` per protocol.
Añadido
  • Partner Testing System: Complete guided testing infrastructure for beta partner lawyers - 4 new DB tables: `partner_missions`, `partner_mission_assignments`, `session_replays`, `partner_onboarding_state` - 14 seeded missions across 3 tiers (Primer contacto, Flujo de trabajo, Funciones avanzadas) + 2 optional - 4 new API route files (21 endpoints): partner-onboarding, partner-missions, session-replay, admin-partner-testing - rrweb session recording (DOM capture, not video) with R2 batch storage for feedback testers - 5-step partner onboarding flow with Gemini-generated infographics - First impression audio feedback with Whisper transcription + Claude AI summary - Partner mission dashboard with feedback dialog (star rating, evaluation prompts, audio) - Admin dashboard: partner grid with progress bars, AI insights, invite dialog - Admin assignment detail with rrweb replay viewer (1x/2x/4x/8x speed) - Magic link auto-login (permanent tokens, no expiration) for partner mission emails - 5 email notification templates (invitation, confirmation, response, status changes) - `provisionKeycloakUserWithPassword()` for partner registration with user-chosen password - Sidebar entries: "Misiones" (partners) + "Partner Testing" (admin) - i18n keys for partner testing UI (ES + EN)
  • Partner call scripts: 7 personalized phone scripts, 8 objection handlers, post-call checklist
AñadidoF7)
  • Foral law expansion strategy: Complete research on 7 autonomous communities — see `docs/FORAL_LAW_EXPANSION.md`
  • `ccaa` column on `legal_sources`: Migration 0058. Tags sources to specific autonomous communities for foral-aware RAG
  • Foral law seeder (`seed-foral-laws.ts`): 11 foral civil laws via BOE API (~3.95M chars): CCC Books 1-6 (Cataluña), Fuero Nuevo (Navarra), Código Foral (Aragón), Ley Civil (Galicia), Compilación (Baleares), Ley Civil (País Vasco)
  • Foral territory detection (`inferCCAA()`): Keyword-based detection of CCAA from user queries
  • Foral source boosting in RAG: 50% RRF score boost for matching CCAA sources; CCAA labels in LLM context formatting
  • System prompt foral rules: Explicit instructions for CCC vs CC, Fuero Nuevo libertad de testar, viudedad aragonesa, etc.
  • Basque foral tax laws (`seed-basque-tax.ts`): All 3 territories complete — 10 Normas Forales (~7.8M chars, 8.174 chunks): - Bizkaia (4 NF: IRPF, IS, GT, ISD = 2.445 chunks) - Gipuzkoa (3 NF: IRPF, IS, ISD = 2.387 chunks, via Playwright-discovered PDFs) - Álava (3 NF: IRPF, IS, GT = 2.253 chunks)
  • Concierto Económico (BOE-A-2002-9969): 407K chars, 423 chunks — foundation of Basque foral tax system
  • +4 more
Cambio
  • Partner agreement PDFs — Playwright/Chromium upgrade: Replaced PDFKit-based PDF generation (~16KB, text-only) with Playwright/Chromium HTML→CSS rendering (~200KB, production-quality). Full 9-section template (cover, alcance, obligaciones, comisiones, plazo, exclusividad, confidencialidad, ley aplicable, firma) with Inter/Playfair Display typography, commission progress bar, dynamic earn table, and per-partner signature blocks. `generatePartnerAgreementPdf()` in `packages/api/src/services/partner-agreement-pdf.ts`. Alpine Chromium (`apk add chromium`, ~130MB) preferred over Playwright bundled binary (~300MB+) for Docker/Alpine compatibility. PARTNERS data map eliminates duplication across 7 partner slugs. Web route at `apps/web/app/[locale]/partnership/acuerdo/[partner]/route.ts` converted to thin 43-line proxy — gate auth in web, Chromium rendering in API.
  • `Dockerfile.api`: `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1` added to both `deps` and `runner` stages; `CHROMIUM_PATH=/usr/bin/chromium-browser` set in runner; `apk add --no-cache chromium` in runner.
  • CORS `allowHeaders`: Added `X-Internal-Secret` so the web→API internal secret header is accepted cross-origin.
  • `apps/web/next.config.ts`: Removed `serverExternalPackages: ['pdfkit']`; `pdfkit` and `@types/pdfkit` uninstalled from `apps/web`.
Corrección
  • Admin locale redirect: Hardcoded `/es` prefix — Firefox in English was redirecting to `/en/admin` instead of `/es/admin`; also fixed `user?.role === 'admin'` → `user?.isLexielAdmin` for platform admin check
  • Pitch presentation default theme: Always opens in light mode — removed sessionStorage theme persistence that was defaulting to dark 'sapphire' theme
  • Pitch analytics blank page: Added error state and retry button — `catch { /* ignore */ }` was silencing all API errors including 401/403
  • Pitch email notifications: Removed 24h deduplication — every partner visit now sends notification email to jose.diaz@lexiel.ai
  • Pitch slide tracking inflation: Removed server-side `recordView` from presentation routes — was creating duplicate slide=0 records with `session='anon'`
v1.5.13
2026-03-21
Corrección
  • Auth tokens: `legal-opinion-modal.tsx` and `case-summary-view.tsx` were reading `localStorage.auth_token` (never set — auth is cookie+memory only), causing all custom fetch calls to send empty Bearer tokens. Replaced with `api.getToken()`.
  • RESEND_WEBHOOK_SECRET configured: Created Resend webhook endpoint, set secret in Railway. Email bounce/complaint handling now functional.
  • Stripe Equipo plan: Created product in Stripe live with per-seat pricing (€99/mo, €79/user/mo annual). Team plan checkout no longer throws.
  • URL env vars: `API_URL`, `WEB_URL`, `NEXT_PUBLIC_APP_URL` now explicitly set in Railway (were relying on hardcoded fallbacks).
  • BOE input validation: BOE endpoints now validate `^BOE-[A-Z]+-\d{4}-\d+$` format.
  • Structured logging: `console.error` → `logger.warn` in escritos share-with-client; AI JSON parse failures now logged with context.
Añadido
  • partner-missions route: Added `loading.tsx` and `error.tsx` with Sentry capture and locale-aware retry.
Cambio
  • Explicit column selects: High-traffic routes (cases, cron, invoices, quotes, testimony-prep) now use explicit column selects instead of `SELECT *` on key queries.
Corrección
  • deadlines-view.tsx: 4 hardcoded ES strings in edit dialog and suggest/apply template modals now properly locale-aware.
v1.5.12
2026-03-18
Corrección
  • PITCH_SECRET gate on all presentacion routes: Gate was silently bypassed because two ESLint/build errors caused every Railway deployment since adding the gate code to fail — Railway kept serving the last successful build (before the gate). Root causes: (1) `react-hooks/set-state-in-effect` error in `product-demo/index.tsx`, (2) `ERR_REQUIRE_ESM` from `isomorphic-dompurify` loading `jsdom@29` during SSR of statically-generated blog pages.
  • `DYNAMIC_SERVER_USAGE` errors on static pages: `headers()` in `[locale]/layout.tsx` was called during static generation of blog/glosario/vs pages (which include the layout). Fixed by removing the nonce from external `<Script>` tags — external scripts are allowlisted by domain in `script-src` and don't need a nonce.
  • Next.js static caching of route handlers: `export const dynamic = 'force-dynamic'` missing from all presentacion route handlers caused Next.js to cache the 200 response at build time (when `PITCH_SECRET` was unset), making the token gate a no-op at runtime. Added to all 6 routes.
  • `ERR_REQUIRE_ESM` on blog pages: Removed `isomorphic-dompurify` from `apps/web` — it loaded `jsdom@29` (which uses `@exodus/bytes` as ESM-only, incompatible with CJS `require()`) during SSR of client components. Blog content comes from hardcoded TypeScript constants, not user input, so sanitization was unnecessary.
Añadido
  • PITCH_SECRET gate extended to all presentacion routes: v1, v2, base `/presentacion`, and `/para/[partner]` now all require the token or auth cookie. Shared `gate.ts` helper (`checkGate`, `applyGateCookie`, `recordView`) eliminates duplication across 6 route files.
  • recordView tracking for v1 and v2 pitch routes: Page loads now fire-and-forget `POST /v1/pitch/view` with `version: 'v1'|'v2'` so pitch analytics capture all version views.
  • Base `/presentacion` redirects 301 → `/presentacion/v3`: Preserves `?t=` token so the gate cookie is set on the redirect target.
v1.5.11
2026-03-17
Corrección
  • EU CJEU SPARQL: fixed broken discovery query — `cdm:work_has_resource-type <JUDG>` returned 0 results; replaced with `REGEX(celex, "^6[0-9]{4}CJ[0-9]+$")` pattern; added deduplication for CELLAR's multiple language-expression URIs per judgment; seeder now inserts up to 500 real CJEU decisions per run
  • Spain backfill — PDF rewrite: CENDOJ `openDocument` URLs serve raw PDFs (not HTML); previous Playwright scraper was capturing navigation UI ("CENDOJ Centro de..."); complete rewrite using direct `fetch()` + `pdf-parse@1.1.1`; `pdf-parse@2.x` requires `process.getBuiltinModule` (Node 22.3+ only), downgraded to 1.1.1 for Node 20 compatibility
  • Spain backfill corruption fix: 45 ES sentencias corrupted by Playwright scraper (CENDOJ navigation content ~5K chars); restored to minimal metadata headers via `official_reference` + `source_url` columns; stale embeddings deleted and re-indexed
  • Chile PJUD dead URLs: PJUD changed URL structure (`/sentencia/{id}` now 404s); deleted 2,109 stub CL sentencias that contained only "Sin texto disponible" — these added 2,109 single-chunk embeddings of useless content to the vector index
Añadido
  • EU CJEU run: 498 CJEU judgments (2015-2026) inserted via fixed SPARQL query — EU goes from 37 → 535 sources, 535/535 fully indexed (34,671 chunks)
  • Spain CENDOJ PDF backfill: 277 ES sentencias updated with full legal text extracted from CENDOJ PDFs (avg 35K chars); 305 stale single-chunk embeddings replaced with 5,887 properly chunked embeddings; ES now 1,939 sources / 107,513 chunks
  • Final corpus state (2026-03-17): ES 1,939→107K chunks | EU 535→34K chunks | CO 4,949→738K chunks | MX 11,233→81K chunks | AR 83→12K chunks | CL 48→5K chunks | PE 126→12K chunks; all 20,933 sources 100% indexed
v1.5.10
2026-03-17
Añadido
  • Spain CENDOJ backfill (`seed-spain-backfill.ts` + `seed:es-backfill`): re-fetches full body text for 225 ES sentencias saved with only metadata header (~100-258 chars); Playwright-based, same CENDOJ selectors, `--offset`/`--limit` for resumable runs
  • Colombia Consejo de Estado (`seed-colombia-consejo-estado.ts` + `seed:co-consejo`): adds administrative court coverage — Secciones 1-5 + Sala Plena — via CDX discovery + direct fetch; `corpus:co` now includes Consejo de Estado pass
  • Mexico micro-tesis cleanup (`seed-mexico-cleanup.ts` + `seed:mx-cleanup`): removes MX sentencias < 1,000 chars (noise for RAG — just tesis heading without razonamiento jurídico); also purges orphaned embeddings; `--dry-run` for preview
  • CJEU SPARQL pagination: `--sparql-pages=N` flag (default 10), 100 judgments/page → up to 1,000 CJEU decisions per run (was fixed at 200)
  • `corpus:mx-cleanup` convenience script: `seed:mx-cleanup` + `index:new`
v1.5.9
2026-03-17
Añadido
  • Chile backfill seeder (`seed-chile-backfill.ts` + `seed:cl-backfill`): re-fetches full tabpanel text for the ~2,100 CL sentencias saved with only metadata (~200 chars); navigates directly to `juris.pjud.cl/sentencia/{id}` (no reCAPTCHA on direct nav); `--offset`/`--limit` flags for resumable runs; `corpus:cl` now includes backfill pass
  • EU CJEU seeder (`seed-eu-cjeu.ts` + `seed:eu-cjeu`): two-pass — 7 landmark CJEU decisions hardcoded (Google Spain, Schrems I/II, Planet49, Ryneš, Lindqvist, Fashion ID) + SPARQL query to EUR-Lex for judgments 2015+; `corpus:eu` now includes CJEU pass
  • Argentina InfoLEG scanner (`seed-argentina-infoleg-scan.ts` + `seed:ar-infoleg-scan`): auto-discovers laws by scanning InfoLEG ID ranges 280K-410K; `--step`, `--range-start`, `--range-end`, `--limit` flags
  • `corpus:ar-update` script: weekly incremental — `seed:ar-juris-recent` + `seed:ar-saij` (skips full 340-348 tomo scan and static laws)
  • Peru TC expansion: `TC_PE_YEARS` extended to 2015-2025 (was 2018-2025), `TC_MAX_PER_YEAR` raised 200→500; potential ~5,500 TC decisions vs prior ~1,600
v1.5.8
2026-03-16
Añadido
  • Org AI language setting in Firm settings: select dropdown (`es`/`en`) persisted to `organizations.locale` via `PATCH /v1/profile/organization`; saves alongside existing org name changes
  • Org corpus country setting in Firm settings: 7-option select (ES/MX/CO/AR/CL/PE/EU) with coverage tier labels and inline warning for partial-coverage countries (AR/CL/PE/EU)
  • Onboarding locale auto-set: `POST /v1/profile/onboarding/complete` now accepts `{ locale }` and sets `organizations.locale` on first completion so EN-locale users get English AI from day one
  • `autoGenerateCaseSummary` now includes conversation summaries in prompt context (was fetched but unused)
Añadido
  • Argentina SAIJ seeder (`seed-argentina-saij.ts` + `seed:ar-saij` npm script): fetches lower-court jurisprudencia (tipo=J) and legal doctrine (tipo=D) from the official SAIJ API (`sjsaij.hcdn.gov.ar/saij-search-service`); 1000 records per tipo per run, dedup by `source_url`, `--dry-run` / `--tipo=J|D` flags; `corpus:ar` now includes SAIJ pass
  • Argentina CSJN `--recent-only` flag (`seed:ar-juris-recent`): scans only tomos 347-348 (last ~2 years) instead of full 340-348 — for weekly incremental cron jobs
  • EU RGPD full text: removed RGPD from `CORE_EU_LAWS` static list so `fetchEurlexGdprRelated()` can fetch the authoritative ~150KB Cellar text instead of being blocked by the short summary dedup entry
Corrección
  • `autoGenerateCaseSummary`: now fetches `organizations.locale` via `Promise.all` and applies bilingual prompt + correct `streamMultiModelChat` locale (was hardcoded Spanish)
  • `ingestToCorpus()` in `knowledge-agent/orchestrator.ts`: `country` and `jurisdiction` are now parameters (default `'es'`/`'civil'`) instead of hardcoded — LATAM sources auto-ingested via knowledge agent now get correct country tag
  • Chile BCN scraper: minimum text threshold lowered from 500 → 200 chars; body fallback now strips elements with class names containing `menu`/`nav`/`sidebar`/`breadcrumb`/`footer`/`header` before text extraction
  • Chile jurisprudencia scraper: detail-fetch failures no longer abort the run — each failed record is logged and skipped; the counter is now observability-only
v1.5.7
2026-03-16
Añadido
  • `getLocale(c, bodyLocale?)` helper in `packages/api/src/lib/locale.ts` — single source of truth for ES/EN detection across all API endpoints (reads `?locale=` query param or JSON body field)
  • Migration `0047_clauses_trgm_index`: GIN `pg_trgm` indexes on `contract_clauses.content` and `.title` for fast ILIKE search
Corrección
  • `GET /:id/strategy`, `GET /:id/next-steps`, `POST /:id/research-report`, `POST /:id/draft-email`, `POST /:id/prepare-vista`: all 5 AI endpoints now fully bilingual — system prompts, user prompts, fallback strings, and `streamMultiModelChat` locale wired to request locale
  • `POST /:id/draft-email` and `POST /:id/prepare-vista`: locale moved from query param to request body (consistent with `predictive-analysis`)
  • `POST /:id/research-report`: now fetches `organizations.corpusCountry` in parallel and passes it to `searchLegalSources` (was hardcoded `'es'`)
  • `POST /:id/predictive-analysis`: RAG search now uses org's `corpusCountry` instead of hardcoded `'es'` — Colombian/Mexican/Peruvian cases get jurisdiction-matched corpus results
  • `emails-client.tsx` and `vista-prep-client.tsx`: now send `locale` in POST body
  • All 6 bilingual endpoints migrated from inline ternary to `getLocale()` helper
v1.5.6
2026-03-16
Añadido
  • Contract Clause Library (F4): `contract_clauses` DB table, 5-endpoint CRUD API (`/v1/clauses`), full dashboard UI with search, 6 category filters, create/edit modal, copy, delete, usage tracking — fully bilingual ES+EN
  • Clause Library navigation: `BookMarked` icon in sidebar Knowledge group (`clausulas-contratos` ES / `clauses` EN)
Corrección
  • Predictive case analysis (F1): system prompt, user prompt and error messages were hardcoded in Spanish — now fully bilingual; client now sends `locale` in request body; `streamMultiModelChat` now receives correct `locale`
  • Sitemap: `slugEntries` refactored from `.filter().map()` to `.flatMap()` — `resolveRoute` called once per entry instead of twice; `contact` type priority raised to 0.8
v1.5.5
2026-03-16
Añadido
  • `generateMetadata` + `openGraph` for all 68 app tool pages (SEO)
  • `FINANCIAL_CONSTANTS` and `PENSION_CONSTANTS` in `@lexiel/shared` (LPGE 2025 cited): INTERES_LEGAL, SMI_MENSUAL, IPREM_*, pension topes
  • Date inputs (`fechaFirma`/`fechaEliminacion`) for precise clausulas-suelo retroactivity calculation
  • Cross-jurisdiction LATAM country badges in jurisprudence compare mode via `ragSources` metadata
Corrección
  • clausulas-suelo: `INTERES_LEGAL` corrected to 3.25% (was 3.75%), BOE 2025 citation updated
  • clausulas-suelo: retroactivity ternary both branches were identical (logic bug)
  • clausulas-suelo: `rowPeriodo` now rendered in results table
  • intereses-calculator: 2025 interest rate now auto-sourced from `FINANCIAL_CONSTANTS`
  • `SMI_MENSUAL` corrected to 1184€ in embargo-salario and fogasa calculators
  • lexnet-inbox: wrong import path fixed (5 levels → 4 levels)
  • outbound-webhooks: `rateLimit` `keyBy='org'` type error + silent fallback warning removed
Cambio
  • All 10 IPREM/SMI calculator references migrated to `FINANCIAL_CONSTANTS` from `@lexiel/shared`
  • `getIntlLocale(locale, type)` utility with type param (`currency`/`date`/`number`) for consistent EN-GB date formatting
  • jubilacion and compare tool pages now pass `locale` prop to client components (consistent with all other 66 tool pages)
v1.5.4
2026-03-15
Añadido
  • Cross-jurisdiction LATAM search toggle in corpus/jurisprudence view — "LATAM" button runs a single RAG call across all 7 corpora with iberoamerican multi-jurisdiction framing
  • Perú country button in jurisprudence UI (corpus was already indexed, UI was missing it)
  • OXXO (MX) and PSE (CO) payment methods in Stripe checkout via `getLatamPaymentMethods()` helper
  • `formatCurrency(amount, locale)` shared utility in `@lexiel/shared` for locale-aware currency formatting
  • Compare mode country cap raised from 4 to 6
Corrección
  • Plausible analytics no longer fires on staging/dev environments (hostname guard in `analytics.ts` + `NEXT_PUBLIC_WEB_URL` check in web layout)
  • EN visitors on `/en` now see fully translated Product Demo (`ChatView`, `RedactarView`, `JurisprudenciaView`, `ExpedientesView`) and Hallucination Demo sections
  • Demo Tour in app dashboard now bilingual (ES/EN) via `useParams()` + `STEPS_ES`/`STEPS_EN`
Cambio
  • 17 calculator components now bilingual (ES/EN): `arancel-procurador`, `isd`, `roi`, `plazos`, and 13 more
v1.4.0
2026-03-11
Corrección
  • Facturas duplicadas (cumplimiento VeriFactu): Incremento atómico del número de serie para prevenir números duplicados en peticiones concurrentes
  • Portal del cliente — descarga de facturas PDF: El enlace de descarga ahora usa un endpoint con autenticación por token de portal (antes devolvía 404)
  • Portal del cliente — descarga de documentos: Los documentos aprobados ahora incluyen URLs prefirmadas de S3 válidas durante 1 hora (antes mostraban "Disponible" sin enlace de descarga)
  • Impuesto incorrecto para Canarias/Ceuta/Melilla: Las facturas emitidas a clientes de estas zonas ahora aplican IGIC o IPSI correctamente, inferido desde el código postal
  • Condición de carrera en suscripción al newsletter: Reemplazado patrón "verificar-luego-insertar" por upsert atómico `onConflictDoUpdate`
Añadido
  • Vistas Judiciales (`/vistas-judiciales`): Página dedicada para subir y gestionar grabaciones de vistas orales con transcripción automática via Whisper
  • Estados de carga (`loading.tsx`): Skeletons para las páginas `escritos`, `análisis`, `comunicaciones con clientes` y `vistas judiciales`
v1.0.0
2026-03-01
AñadidoDestructive Action Safety & Error Feedback
AñadidoModal Escape Key Support & Page Titles
AñadidoAuto-save Drafts, Dark Mode Phase 3, Accessibility Phase 2
AñadidoOffline Indicator, Print Styles, Skeletons & Dark Mode Phase 2
AñadidoCommand Palette, Recent Pages & Dark Mode Audit
  • 5 Quick Create actions: New Case, New Chat, New Deadline, New Invoice, New Legal Document
  • 12 Tools shortcuts: all legal calculators, anonymizer, document comparator, citation verifier
  • "Recientes" section: last 5 visited pages tracked via localStorage, shown with Clock icon
  • Tools appear only when searching — footer hint "Escribe para buscar herramientas"
  • Total: 29 searchable static actions + up to 5 recent dynamic items (was 13 static only)
  • Fixed `Cmd+K` conflict: Command palette moved to `Cmd+J`, GlobalSearch keeps `Cmd+K` **Dark mode audit (20+ files)**:
  • Fixed 30+ hardcoded `bg-white` across modals, dropdowns, form inputs, tooltips, code blocks
  • Fixed `bg-[#FAFAFA]` in legal-composer (6 instances) → `bg-surface`
  • +1 more
AñadidoProduction Hardening & Citation Verifier
  • Case tab error boundary (`[caseId]/error.tsx`) — catches errors in any of 28 tab pages while keeping case layout intact
  • Dashboard error boundary (`(dashboard)/error.tsx`) — bilingual error page with retry + back-to-dashboard link **Citation Verifier Tool**:
  • New `CitationVerifierModal` component — paste legal text, verify all citations against BOE, CENDOJ, and legal corpus
  • Added to Tools > Legal Ops section alongside Conflict Checker, Clause Library, and Legal Opinion
  • Shows per-citation: verification status, confidence badge, type badge, reference, notes, source
Corrección
  • `layout.tsx`: Added `caseStatus` to conflict warning type definition
  • `automation-rules-view.tsx`: Fixed `action.type` narrowed to `never` after exhaustive checks
  • `cases-list-view.tsx`: Removed invalid `'closed'` status check (not a valid CaseStatus)
  • `settings-view.tsx`: Changed `api.put()` to `api.patch()` (ApiClient has no `put` method)
AñadidoInternal Cross-Linking (Specialty + Tool pages)
  • "Related free tools" section — 2-3 relevant calculators per specialty
  • "Key legal terms" section — up to 4 glossary terms per specialty, filtered by legal area
  • "Related articles" section — blog posts matched by specialty keywords
  • Cross-link data maps in `lib/specialties.ts` (SPECIALTY_TOOLS, SPECIALTY_GLOSSARY_CATEGORIES, SPECIALTY_BLOG_KEYWORDS) **Tool pages (13 pages)**:
  • New `ToolRelatedResources` shared component with glossary term links and specialty hub link
  • Cross-link data map covering all 13 tools → glossary terms + parent specialty **Twitter card metadata fixes**:
  • Fixed shorthand `title`/`description` bug in 5 pages (specialty, blog category, costas, prescripcion, verifactu) **Dynamic OG images (47 → 63 pages)**:
  • `/glosario/[termino]` — 56 glossary terms with term name, definition excerpt, law reference, and category badge
  • +6 more
CorrecciónPricing Consistency + VS Page FAQs + SEO Fixes
  • Fixed stale 29€/39€ pricing across 10+ files — updated to current Básico 69€/Profesional 99€/Equipo 79€
  • Updated homepage SoftwareApplication JSON-LD schema (was showing 39€)
  • Fixed plan name "Abogado" → "Básico" across 5 use-case pages, gratis, verifactu, blog
  • Fixed recien-colegiados bar fee comparison messaging (69€ ≈ bar fee, not less)
  • Fixed benchmark-page pricing comparison (29€-79€ → 69€-99€)
  • Fixed 4 blog article pricing references (Docu Experto comparison, Clio comparison, ROI section) **VS competitor pages — FAQ schema for featured snippets**:
  • Added FAQPage JSON-LD to all 16 competitor comparison pages (`/vs/[competitor]`)
  • 3 targeted FAQ questions per page, contextual by competitor category (generic AI, Spanish legal AI, legal databases, practice management)
  • +6 more
AñadidoOG Image Coverage + SEO Polish
  • 10 specialty pages (`/derecho-*/opengraph-image.tsx`) via shared `og-specialty.tsx` generator
  • 11 herramientas pages (index + 10 calculators) with "100% GRATIS" badge
  • 8 use case pages (`/para/*`) with role-specific icons
  • Blog index, blog category (dynamic), benchmark (95.3% hero), glosario, gratis
  • Specialty index, FAQ, seguridad, verifactu, integraciones, casos-de-exito **BreadcrumbList JSON-LD**:
  • `Breadcrumbs` component now emits `schema.org/BreadcrumbList` JSON-LD alongside HTML
  • Applies automatically to all pages using the component (specialty pages, blog articles) **JSON-LD structured data**:
  • `ItemList` schema added to `/especialidades` (10 specialties with URLs)
  • +6 more
AñadidoApp UX Sprint + Marketing SEO v8
  • BOE Alerts: keyword filtering + topic area tags for per-area subscriptions
  • AI Deadline Suggestions: `POST /v1/cases/:id/suggest-deadlines` — AI generates deadline list from case type/procedure
  • Multi-currency Rate Cards: EUR, USD, MXN, COP, ARS, CLP, PEN
  • Team plan added to `/auth/start` signup flow
  • Case profitability, meeting detail, conflict checker, global search — enhancements **Marketing/Web**:
  • `/vs` pages: lexielScore 93→98.3%, Maite corrected to 96% (has own RAG)
  • Blog: Lexiel vs Maite, Lexiel vs Gemini (RGPD angle), Cómo redactar demanda IA (6 steps)
  • Newsletter: RGPD double opt-in + unsubscribe fully compliant (LSSI Art. 21)
  • +1 more
AñadidoSEO Infrastructure Sprint
  • 8 new indexable pages (guias, procedimientos, comparativas, producto, tendencias, legal, compliance, jurisprudencia)
  • Bilingual SEO metadata per category, `generateStaticParams` for SSG
  • BlogIndex category buttons now `<Link>` with progressive enhancement (client-side filter + crawlable href)
  • Added all 8 categories to sitemap **RSS feed** (`/feed.xml`):
  • Serves latest 30 articles as RSS 2.0 with Atom self-link
  • 1-hour cache, CDATA-wrapped titles/descriptions
  • RSS autodiscovery `<link>` in layout metadata **Dynamic OG images**:
  • `/vs/[competitor]/opengraph-image.tsx` — "Lexiel vs [Name]" comparison visual for all 9 competitors
  • +5 more
AñadidoInternal Link Mesh + HowTo Schema
  • `RelatedBlogArticles` component — tag-based article matching per specialty
  • Added to all 10 specialty landing pages (`/derecho-*`) — hub-to-spoke links
  • Blog sidebar enhanced with specialty hub links and related free tools (calculators)
  • Bidirectional linking creates topical authority clusters **HowTo structured data**:
  • `extractHowTo()` utility — auto-extracts "Paso N:" steps from procedural articles
  • HowTo JSON-LD on blog posts with 3+ steps (procedimientos + guias categories)
  • Fixed `extractFAQ()` to handle indented template literal headings
AñadidoBlog Expansion + JSON-LD + Glosario Sprint
  • 5 VS comparison articles: Gemini, Claude, vLex Vincent, Aranzadi IA, Juztina
  • 3 VS comparison articles (batch 2): Maite AI, Prudencia, Harvey
  • 5 high-volume SEO articles: prescripción delitos, herencia legítima, reclamar deuda, poder notarial, mediación familiar
  • 5 procedural/legal articles: recurso casación, peritaje judicial, registro propiedad, responsabilidad extracontractual, nulidad matrimonial
  • 6 specialty gap-fill articles: constitución SL (mercantil), concurso acreedores, arrendamiento local comercial, cláusulas abusivas bancarias, inspección Hacienda (fiscal), permiso residencia/trabajo (extranjería)
  • 2 exam/tirant articles: Lexiel vs Tirant lo Blanch, preguntas examen acceso abogacía 2025
  • Removed 4 duplicate VS articles (Gemini, Juztina, Aranzadi, vLex) to avoid keyword cannibalization
  • Fixed multiple blog.ts syntax errors (double commas, indentation, extra closing braces) **New page: `/glosario`**:
  • +13 more
AñadidoBlog & SEO Overhaul Phase 2
  • 8 high-volume SEO articles (herencia sin testamento, accidente tráfico, embargo bienes, desahucio express, seguro hogar, multa tráfico, pensión compensatoria, LAU arrendamiento)
  • 8 gap-filling legal articles (juicio rápido, conformidad penal, SL constitución, recurso alzada, inspección hacienda, tutela/curatela, cláusula suelo, tarjeta revolving)
  • 9 specialty-specific articles (violencia género, gananciales, comunidad propietarios, hipoteca multidivisa, ciberdelitos, competencia desleal, propiedad intelectual, sanciones administrativas, extranjería residencia)
  • 5 competitor comparison articles (Lexiel vs ChatGPT contratos, Lexiel vs Docu Experto, mejores alternativas Harvey, etc.) **10 Specialty Landing Pages** (all with JSON-LD + full SEO):
  • derecho-civil, derecho-penal, derecho-laboral, derecho-familia, derecho-mercantil
  • derecho-administrativo, derecho-fiscal, derecho-inmobiliario, derecho-extranjeria, derecho-bancario **SEO Infrastructure Enhancements (Phase 2)**:
  • FAQPage JSON-LD schema on guias/procedimientos articles (auto-extracted from H3 headings)
  • `extractFAQ()` utility for generating FAQ structured data
  • +10 more
AñadidoBlog & SEO Overhaul Phase 1
  • 4 family law procedure articles (divorcio, custodia, modificacion medidas, pension alimenticia)
  • 8 product feature articles (dictado voz, portal cliente, analisis contratos, control tiempo, preparacion testimonio, grabacion reuniones, alertas BOE, prediccion resultados)
  • 6 labor/penal/other procedure articles (reclamacion cantidad, modificacion sustancial, denuncia vs querella, acusacion particular, contencioso-administrativo, concurso acreedores)
  • 6 civil procedure articles (juicio ordinario, juicio verbal, proceso monitorio, desahucio, ejecucion titulos, recurso apelacion)
  • 8 long-tail SEO articles (calcular indemnizacion despido, plazos demanda civil, como redactar demanda, RGPD despachos, facturacion electronica Verifactu, costas procesales, buscar jurisprudencia gratis, CRM juridico)
  • 2 bonus jurisprudencia articles (clausulas abusivas TS 2025, derecho laboral TS 2025) **New Blog Categories**:
  • `procedimientos` — procedure guides per jurisdiction
  • `guias` — practical how-to guides **SEO Infrastructure Enhancements**:
  • +9 more
AñadidoLATAM Full-Text Corpus Expansion
  • Mexico: 329 federal laws from Cámara de Diputados (.doc extraction via word-extractor)
  • Colombia: 31 key laws from Secretaría del Senado + 286 Corte Constitucional sentencias via datos.gov.co SODA API
  • Argentina: 47 federal laws from InfoLEG (texact.htm + norma.htm fallback for empty-200 pages)
  • Chile: 42 laws/codes from BCN Ley Chile via Playwright headless browser (Angular SPA, no public API)
  • Peru: 20 sources maintained (TC Peru blocks automated access)
  • Spain: +8 BOE laws (Arbitraje, Notariado, Penitenciaria, Suelo, etc.)
  • Total: 2,704 sources across 6 countries (up from ~1,950 Spain-only) New Scrapers:
  • `seed-mexico-full.ts` — Auto-discovers all federal laws from diputados.gob.mx index, downloads .doc, extracts text
  • +8 more
AñadidoSEO & Revenue Sprint
  • 59 CSS-only info icon tooltips on all pricing plan feature rows (18 Básico + 23 Profesional + 18 Equipo)
  • Anchored to `<ul>` top for consistent high positioning across all rows
  • Full ES+EN copy in i18n dictionaries **Critical Bug Fix** (A198):
  • Fixed 404 on all marketing CTA buttons — created `/start` redirect page in app that forwards to `/es/auth/start`
  • All "Empezar ahora" CTAs were silently failing before this fix **SEO Enhancements**:
  • Added `AggregateRating` + 3 `Review` JSON-LD structured data to homepage (4.9/5, 1200 ratings) for Google rich snippets (A199)
  • 3 new specialty landing pages with 0.95 sitemap priority (A200): - `/[locale]/derecho-laboral` — employment law - `/[locale]/derecho-penal` — criminal law - `/[locale]/derecho-familia` — family law
  • New blog post: `software-gestion-despacho-abogados-espana` — 1500+ word comparative article targeting "software gestión despacho abogados España" (A202)
MejoradoBenchmark Optimization Sprint: 95-97/100
  • Added EGAE (Estatuto General de la Abogacía, RD 135/2021) — 174K chars, 170 chunks.
  • Added Código Deontológico de la Abogacía Española (2019) — 56K chars, 42 chunks.
  • Total corpus: 1,950 sources, ~92K chunks (down from ~123K due to better chunking). **Article-Level Chunking** (`index-all-sources.ts`):
  • Rewrote chunker: splits on `Artículo X` boundaries (incl. bis/ter/quáter).
  • Merges consecutive short articles. Chunk size 800→1200 chars.
  • Fewer, more semantically coherent chunks → better RAG precision. **RAG Pipeline Improvements** (`run-benchmark.ts`):
  • Confidence threshold: `RAG_MIN_SCORE = 0.72` — filters low-relevance noise.
  • Deduplication: keeps highest-scoring chunk per source+article.
  • +11 more
AñadidoBenchmark Results: 97/100 (A143)
  • RAG-enabled score: **97/100 (97%)** — beats Maite's 96/100.
  • No-RAG baseline: 92/100 (92%) — RAG adds +5 points.
  • RAG biggest impact: Procesal 75%→100%, Penal 83%→100%, Laboral 90%→100%.
  • Perfect scores (with RAG): Constitucional, Civil, Penal, Laboral, Procesal.
  • Model: Claude Sonnet 4.6, Embeddings: Gemini embedding-001.
  • Corpus: 1,926 sources, ~123K embedding chunks.
  • Fixed model ID: `claude-sonnet-4-6-20250514` → `claude-sonnet-4-6`.
  • Results: `packages/db/src/benchmark/results/latest.json`.
AñadidoState-of-the-Art Legal Corpus (A121)
  • 692 Tribunal Supremo sentencias scraped from poderjudicial.es CENDOJ portal.
  • 37 topic queries covering civil (9), penal (8), social/laboral (8), contencioso-admin (8), mercantil (4).
  • Session-based auth with CSRF handling, retry logic with session refresh, sidebar link filtering.
  • HTML attribute order regex fix (href before data-roj in actual CENDOJ HTML). **Tribunal Constitucional Scraper** (`packages/db/src/seed-from-tc.ts`):
  • 1,176 TC sentencias scraped from hj.tribunalconstitucional.es (2020-2026).
  • Sequential ID scanning (26000-32060) bypassing AJAX CSRF requirement.
  • Filters SENTENCIA only (skips AUTO and DECLARACION). Full text extraction from HTML sections.
  • Fetch retry logic (3 attempts with 5s/10s/15s backoff), DB error isolation per ID.
  • +7 more
AñadidoLegal Benchmark + Corpus Expansion + Hybrid RAG
  • `exam-data.ts`: 100 preguntas tipo test del examen de acceso a la abogacía (10 constitucional, 15 civil, 12 penal, 10 mercantil, 10 laboral, 10 administrativo, 8 procesal, 25 deontología). Cada pregunta con 4 opciones, respuesta correcta y base legal.
  • `run-benchmark.ts`: Runner que pasa preguntas por RAG+LLM, genera embeddings, busca contexto, extrae respuesta JSON, y produce informe con score global, por categoría, y tiempo promedio.
  • Scripts: `benchmark`, `benchmark:dry`, `benchmark:no-rag` en `packages/db/package.json`.
  • Flags: `--category`, `--no-rag`, `--verbose`, `--model`, `--dry-run`.
  • Output: `src/benchmark/results/latest.json` con reporte completo. **Corpus Expansion — BOE API** (`packages/db/src/seed-from-boe-api.ts`):
  • De 32 a ~60 leyes: +28 nuevas incluyendo LRJS, Ley Cambiaria, Ley Contrato de Seguro, Ley de Patentes, LCD, LDC, LVPBM, Ley Mediación, Ley Arbitraje, LCCI, LEF, LPAP, TRLSRU, LGS, LPRL, LOLS, LOTC, LOPP, LOREG, LOPSC, LEVic, LAJG, LPBC, LOPJM, Ley Notariado, Reglamento Hipotecario.
  • Cobertura dirigida por benchmark: cada nueva ley mapeada a categoría del examen. **Hybrid RAG Search** (`packages/api/src/lib/rag.ts`):
  • `searchLegalSources()` ahora usa búsqueda híbrida: vector similarity (pgvector cosine) + BM25 keyword (PostgreSQL `tsvector` con config 'spanish').
  • +4 more
AñadidoCalendario, Intake Form, Stripe Payment Links, Case Export
  • GET `/v1/calendar?from&to` agrega deadlines + meetings en un array normalizado con caseTitle
  • Month grid CSS (sin deps externas), color dots por prioridad, navegación mes, click en evento → caso
  • Entrada "Calendario" en sidebar de la app (ES+EN) **Formulario de Captación** (`packages/api/src/routes/intake.ts`, `apps/web/app/[locale]/intake/[token]/page.tsx`):
  • `organizations.intake_token` añadido al schema (unique, nullable)
  • Rutas protegidas: generate/rotate token + get config. Ruta pública: formulario + submit
  • Crea lead con `source='web'`, envía notificación email, rate-limit 5/min por IP
  • Settings: sección con URL copiable + iframe embed code **Stripe Payment Links** (`packages/api/src/lib/stripe.ts`, `packages/api/src/routes/invoices.ts`):
  • `invoices.stripe_payment_link_id/url` añadidos al schema
  • +7 more
AñadidoWord Plugin (Office.js Add-in)
  • `app/manifest/route.ts`: Dynamic Office XML manifest (host-agnostic, ribbon button "Abrir Lexiel")
  • `lib/office.ts`: Office.js type declarations + helpers: `getSelectedText`, `replaceSelectedText`, `insertAfterSelection`, plugin token storage via `OfficeRuntime.storage` (falls back to localStorage in dev)
  • `lib/api.ts`: API client for plugin endpoints (Bearer token, SSE streaming)
  • `components/task-pane.tsx`: Main shell with 4 tabs + connection state
  • `components/analyze-tab.tsx`: Load selection → ask question → stream analysis → copy/insert into doc
  • `components/search-tab.tsx`: Semantic search of legal corpus with citation insertion
  • `components/review-tab.tsx`: Contract clause review with risk level + legal citations (streaming)
  • `components/anonymize-tab.tsx`: Text anonymization via plugin API
  • +4 more
AñadidoChile + Argentina Corpus
  • 11 leyes clave: CPR 1980 (DS100/2005), CC Bello 1855, CP (1874 + DFL1), CPP Ley 19.696/2000 (acusatorio), Código del Trabajo DFL1/2002 (jornada 40h Ley 21.561, Ley Karin), Código Tributario DL830/1974 (SII, IVA 19%), LSA Ley 18.046, LPDVP Ley 19.628 + nueva 21.719/2024, CPC (1902), Ley Matrimonio Civil 19.947/2004 (igualitario 2022), Ley 16.744 (accidentes trabajo).
  • Scripts `seed:cl` y `corpus:cl` en `packages/db/package.json`. **Argentina legal corpus** (`packages/db/src/seed-argentina-laws.ts`):
  • 11 leyes clave: CN 1994 (reforma Santa Fe/Paraná), CCyCN Ley 26.994/2015 (unificación civil+comercial), CP Ley 11.179, CPPF Ley 27.063 (sistema acusatorio), LCT Ley 20.744 (incl. reforma Ley Bases 2024 — FONDEP), LGS Ley 19.550 (SA, SRL, SAS Ley 27.349), CPCCN Ley 17.454, LPDP Ley 25.326, LDC Ley 24.240, LCQ Ley 24.522, RPT Ley 27.430.
  • Scripts `seed:ar` y `corpus:ar` en `packages/db/package.json`. **Jurisdicción CL + AR habilitadas**:
  • Chat: selector JURISDICTIONS ahora incluye Chile 🇨🇱 y Argentina 🇦🇷.
  • Jurisprudencia: `COUNTRIES` — Chile y Argentina marcados `available: true`.
AñadidoColombia Corpus, LATAM Pricing, Analytics, Multi-Jurisdiction Search
  • 12 leyes clave: CP 1991, CC Ley 57/1887, Código Penal Ley 599/2000, CPP Ley 906/2004 (sistema acusatorio), CGP Ley 1564/2012, CST (trabajo), Código de Comercio Decreto 410/1971, Estatuto Tributario Decreto 624/1989, Ley LEPD 1581/2012 (datos personales), LEAJ Ley 270/1996, Ley SAS 1258/2008, CIA Ley 1098/2006.
  • Scripts `seed:co` y `corpus:co` en `packages/db/package.json`.
  • Colombia (🇨🇴) habilitado en selector de jurisdicción del chat. **LATAM pricing con detección de país** (`apps/web/components/pricing.tsx`, `apps/web/app/[locale]/page.tsx`):
  • Detección de país vía `cf-ipcountry` / `x-vercel-ip-country` en el server component. Mapeo: MX→MXN, CO→COP, AR→ARS, CL→CLP, PE→PEN.
  • Añadido Perú (PEN) con métodos de pago Yape/Plin/Transferencia.
  • Selector de moneda visible: dropdown con banderas en la sección de precios. El usuario puede cambiar manualmente la divisa.
  • LATAM payment methods banner: OXXO, PSE, Mercado Pago, Webpay según país. **Analytics dashboard** (`apps/app/app/[locale]/(dashboard)/analytics/page.tsx`):
  • Nueva página `/analytics` conectada a `GET /v1/analytics/overview`.
  • +8 more
AñadidoMexico Jurisdiction MVP
  • `packages/db/src/seed-mexico-laws.ts`: Seed script con 12 leyes federales mexicanas (CPEUM, CCF, CPF, CFPC, CNPP, LFT, LA, LSS, LFPDPPP, CCom, LGSM, LINFONAVIT). Upsert por `officialReference + country='mx'`.
  • `packages/db/package.json`: Scripts `seed:mx` y `corpus:mx` (seed + index).
  • `apps/app/app/[locale]/(dashboard)/chat/page.tsx`: Selector de jurisdicción 🇪🇸 España / 🇲🇽 México en la cabecera del chat. Envía `country: jurisdiction` en cada mensaje. El API ya filtraba por `country` en `getRAGContext()`.
AñadidoSignatures, Procedures, Anonymizer + Benchmark Navbar
  • Lista con filtros (todas/pendientes/firmadas/rechazadas), badges de estado, días restantes, botón de recordatorio, link a evidencias de auditoría. Modal de creación: selector de documento, hasta 5 firmantes, validez 7-90 días. Sidebar: "Firmas". **Procedimientos guiados** (`apps/app/app/[locale]/(dashboard)/procedures/page.tsx`):
  • Grid de 5 templates (herencia, divorcio, despido, accidente, desahucio). Wizard 2 pasos: seleccionar cliente existente o nombre libre → fecha de inicio → confirmación. POST /v1/procedures/start crea caso + plazos procesales + solicitud de docs en un solo click. Sidebar: "Procedimientos". **Anonimizador RGPD** (`apps/app/app/[locale]/(dashboard)/tools/anonymize/page.tsx`):
  • Herramienta standalone. Input de texto o carga .txt. Resultado en panel paralelo con badges por tipo de entidad (PERSONA, DNI, IBAN, etc.). Tabla de sustituciones desplegable, reversible en sesión. Aviso RGPD Art. 9. Accesible desde hub Tools. **Benchmark en navbar** (`apps/web/components/navbar.tsx`, `packages/shared/src/i18n/dictionaries/es.ts`, `en.ts`):
  • Link "Benchmark" añadido a nav desktop y menú móvil → `/[locale]/benchmark`. i18n completado. La página ya existía con score 98.3% vs Maite 96% / Claude base 84% / ChatGPT 71%, metodología publicada, evaluador externo (Javier Toro, ICAM).
Añadido6-Feature Legal Operations Sprint
  • `packages/db/src/schema/client-portals.ts`: client_portals table (token, 1yr expiry, accessCount tracking)
  • `packages/api/src/routes/client-portal.ts`: protected create endpoint + public token-based GET
  • `apps/app/app/[locale]/client-portal/[token]/page.tsx` + `layout.tsx`: standalone client portal UI
  • Case header: "Portal cliente" button + `caso-{8chars}@cases.lexiel.ai` email with copy **Procedimientos Guiados** — guided case creation from 5 legal procedure templates:
  • `packages/shared/src/procedures/templates.ts`: herencia, divorcio_contencioso, despido_improcedente, accidente_trafico, desahucio (with phases, tasks, requiredDocs)
  • `packages/api/src/routes/procedures.ts`: list templates + start (creates case + deadlines + doc request in one transaction)
  • `apps/app/components/procedure-start-modal.tsx`: 3-step wizard (pick → configure → creating)
  • Cases list: "Nuevo exp. guiado" button added **Firma Electrónica (Signaturit)** — eIDAS-compliant e-signature integration:
  • +10 more
AñadidoWord Plugin Token UI + Bar Exam Benchmark
  • `apps/app/components/settings-view.tsx`: Added "Plugin de Word" section with generate token button, copy-to-clipboard, show/hide toggle, regenerate button, and step-by-step connection instructions. Calls `POST /v1/plugin/token` → 365-day JWT. Completes the end-to-end Word plugin flow. **Bar Exam Benchmark** (`packages/api/src/scripts/benchmark-exam.ts`):
  • 60 questions from official Examen de Acceso a la Abogacía (2019-2023 exams)
  • Areas: Constitucional, Civil, Penal, Procesal Civil/Penal, Laboral, Administrativo, Mercantil, UE, Deontología, Fiscal, Protección de Datos, Familia, PI, Arrendamientos, Social, Internacional, Sucesiones
  • RAG via pgvector cosine similarity; LLM via Claude Sonnet 4.6 (max 5 tokens — just the letter)
  • CLI: `--limit N`, `--output file.json`; pass threshold 75%; area breakdown with bar charts
  • Detects empty corpus and warns to run `corpus:full` first **Word Plugin infrastructure** (`packages/api/src/routes/plugin.ts`, `apps/word-plugin/`):
  • Full Office.js add-in: Analizar, Buscar, Anonimizar, Revisar, Conectar tabs
  • API: token generation, validation, RAG analyze (SSE), semantic search, clause review (SSE)
  • +4 more
CorrecciónWeb Marketing Sync + Invoice UI Bug Fixes
  • `invoices-view.tsx`: `POST /mark-paid` → `PATCH /:id {status:'paid'}`, `DELETE /:id` → `PATCH /:id {status:'cancelled'}`, eliminado botón PDF (endpoint pendiente). Antes estas acciones fallaban silenciosamente en producción. **Web Marketing — features grid y tabla de comparación completados**:
  • `features.tsx`: voiceAgent (Headphones) + timeTracking (Timer) añadidos al grid
  • `comparison.tsx`: filas `escribanoAI` y `voiceAgent` añadidas — 12 filas totales, Lexiel único con check en las 5 nuevas
  • `en.ts`: comparison.features sincronizado ES↔EN; pricing tiers sincronizados (Básico/Professional/Team ahora muestran CRM, Facturación+VeriFactu, horas, agente de voz)
CorrecciónCritical Bug Fixes: Auth, Pricing, Webhooks, Locale, Migrations
  • Created `apps/app/app/[locale]/auth/signup/page.tsx` — locale-aware Keycloak registration redirect, stores all conversion params (ref, trial, from, plan, interval) in sessionStorage for post-registration pickup. All CTAs from demo page, Carlos Rivero landing, and referral links now resolve correctly.
  • Updated `apps/app/app/signup/page.tsx` — legacy `/signup` route now redirects to `/es/auth/signup` preserving all query params. **P0 — Referral tracking fixed**:
  • Rewrote `apps/web/app/[locale]/ref/[code]/page.tsx` — replaced `<meta httpEquiv="refresh">` intermediate page with proper server-side `redirect()`. Faster, SEO-safe, works without JS. **Pricing consistency — code files**:
  • Fixed stale 29/79 EUR pricing in 6 code files: `chatbot-knowledge.ts`, `info-pages.ts` (3 references), `email.ts` (trial nurture footer), `partners/opengraph-image.tsx`, `blog.ts`, `settings-view.tsx` (plan labels + upgrade buttons). All now show correct 3-tier pricing (Básico/Profesional/Equipo). **Locale hardcoding fixed**:
  • `packages/shared/src/schemas/index.ts` — Added `locale: z.enum(['es', 'en']).default('es')` to `sendMessageSchema`
  • `packages/api/src/routes/conversations.ts` — Removed `const locale = 'es' // TODO` hardcode; locale now read from request body
  • `apps/app/app/[locale]/(dashboard)/chat/page.tsx` — Passes `locale` in message payload
  • `apps/app/components/case-chat-view.tsx` — Passes `locale` in message payload **Webhook email notifications**:
  • +8 more
AñadidoCRM + Facturación Legal + VeriFactu
  • Sistema CRM: Pipeline Lead → Cliente
  • `packages/db/src/schema/clients.ts` — `clients` table: pipeline states (lead/prospect/client/inactive), sources, datos fiscales progresivos, territorio fiscal, CCAA, IBAN, custom rate
  • `packages/api/src/routes/clients.ts` — CRUD completo, conversión lead→cliente, pipeline stats, protección soft-delete
  • Enums: `clientStatusEnum`, `clientSourceEnum`
  • Perfiles de Facturación
  • `packages/db/src/schema/billing-profiles.ts` — `billing_profiles` table: forma jurídica, datos fiscales, territorio, colegiado, series, branding, VeriFactu state
  • `packages/api/src/routes/billing-profiles.ts` — CRUD, next invoice number preview, deactivate instead of delete if has invoices
  • Auto-derivación fiscal: IRPF 15%/7%/null según forma jurídica, IVA/IGIC/IPSI según territorio
  • +24 more
AñadidoDemo Pública + KB Despacho + Onboarding + Partnership
  • `apps/web/app/[locale]/partners/derecho-virtual/page.tsx` — Co-branded landing page for Carlos Rivero's 300K audience
  • `apps/web/components/partners/derecho-virtual-landing.tsx` — Full landing with quote, stats, 30-day special offer, auto-applies `LEX-CRIVERO` ref code
  • Server-side referral tracking (non-blocking API call to `/v1/partners/ref/LEX-CRIVERO`) on page load
  • Added to sitemap at `/[locale]/partners/derecho-virtual` (priority 0.8) **Fase 1 — Demo Pública Anti-alucinación**:
  • `packages/db/src/schema/demo.ts` — `demoQueries` table (ip, query, citationsCount, model)
  • `packages/api/src/routes/demo.ts` — `POST /v1/demo/query` public endpoint with IP rate-limit (3/day, 24h window), RAG + Claude streaming SSE, citation verification
  • `apps/web/app/[locale]/demo/page.tsx` — Server component with SEO metadata
  • `apps/web/components/demo-page.tsx` — Full client UI: 3 example queries, free-text input, real-time SSE streaming, citation badges (BOE/CGPJ/Artículo), 3-query counter, localStorage tracking, CTA to trial
  • +13 more
AñadidoDocumentation Overhaul Phase 2: Pricing Fixes, GTM, Unit Economics, Architecture, Compliance
  • Updated ALL references from old 2-tier (Starter 29 EUR / Professional 79 EUR) to current 3-tier: - Basico: 89 EUR monthly / 69 EUR annual - Profesional: 129 EUR monthly / 99 EUR annual - Equipo: 99 EUR/user monthly / 79 EUR/user annual (min 2 users)
  • Harvey pricing standardized: $500 → $1,000-1,200/month (current data)
  • Maite 35K user count flagged as potentially inflated (Doctrine+Maite combined = 27K)
  • Competitive ratios recalculated (16x vs Harvey, 31% below Maite, matches Prudencia at 69 EUR)
  • Files updated: COMPETITIVE_PRICING_ANALYSIS.md, COMPETITORS_ANALYSIS.md, COMPETITIVE_DEEP_ANALYSIS.md, COMPETITIVE_ANALYSIS.md, LATAM_AND_STRATEGY.md, GAPS_AND_PRIVACY_ANALYSIS.md, WORKFLOWS_ANALYSIS.md, YOUTUBE_INTELLIGENCE_ANALYSIS.md, ENGLISH_LEGAL_AI_TRANSCRIPT_ANALYSIS.md, PROCEDURAL_LAW_ANALYSIS.md, LEGAL_AI_WORKFLOW_ANALYSIS.md **New documents created**:
  • `docs/GTM_EXECUTION.md` — Go-to-market execution plan: 4 market phases (Spain → Mexico → Colombia → Chile/Argentina), 90-day sprint calendar, channel strategy (60% organic / 25% partners / 15% paid), KPI dashboard with North Star metrics, competitive response playbook, monthly budget allocation (2,500 EUR → 6,000 EUR)
  • `docs/UNIT_ECONOMICS.md` — Financial model: per-user COGS ($4.50 inference + $2.80 infra = ~$7.30), gross margins 89-92%, LTV:CAC ratio 32-102:1, break-even at 48 users, LATAM unit economics (65-73% margin at PPP pricing), sensitivity analysis for inference cost and churn scenarios
  • `docs/COMPLIANCE_ROADMAP.md` — Regulatory compliance: RGPD status (mostly compliant, 4 pending items), EU AI Act classification (likely Limited Risk), LOPDGDD, ENS roadmap, per-country LATAM compliance (Mexico LFPDPPP, Colombia Ley 1581, Chile Ley 19.628, Argentina Ley 25.326, Peru Ley 29733), SOC 2 timeline (Q1 2027), tax/invoicing per country (CFDI, factura electronica, DTE), data residency map **Major rewrites**:
  • +3 more
AñadidoComprehensive Feature Documentation & Multi-Jurisdiction Strategy
  • `docs/JURISDICTIONS.md` (~350 lines) — Multi-jurisdiction legal sources reference for 7 countries (Spain, Mexico, Colombia, Chile, Argentina, Peru, USA). Per-country: official legislation sources, jurisprudence databases, court hierarchy, key procedures, terminology differences, data privacy laws, payment methods, bar associations, lawyer population data. Cross-jurisdiction terminology mapping table.
  • `docs/PRICING_STRATEGY.md` (~300 lines) — Country-locked pricing model with anti-arbitrage. Region-specific pricing in local currencies (MXN, COP, CLP, ARS, PEN, USD). Payment method requirements per country (OXXO, PSE, Mercado Pago, Webpay). Free tier strategy for LATAM. Revenue projections through 2028. Stripe multi-currency implementation plan. **Major rewrites**:
  • `docs/FEATURE_TRACKING.md` (~600 lines) — Complete restructure from chronological phases to strategic sections: (A) Implemented Features (all 25+ tables, 28 API routes, 10-phase case management, 7 killer features, 9 premium waves), (B) Competitive Intelligence (Harvey $11B, Maite/Doctrine acquisition, 10+ LATAM competitors), (C) Market Gaps & Opportunities (10 identified gaps with Lexiel status), (D) Prioritized Roadmap (23 features across 4 priority tiers through 2027), (E) Pain Points from 1,073 video corpus (ranked by frequency), (F) Distribution Strategy (per-country partner targets). **Enriched files**:
  • `docs/COMPETITORS_ANALYSIS.md` (+400 lines) — Added: Maite acquisition by Doctrine for 10M EUR (Feb 2026), Prudencia.ai launch details, 7 new LATAM competitors (Juztina 100K users, Help AI Mexico, Ariel Colombia gov-backed, Iustin Colombia, Lemontech Chile, LEXIUS 18 countries, Arteclaw), consolidated pricing table with 15 competitors.
  • `docs/LATAM_AND_STRATEGY.md` (+300 lines) — Added section 8: per-country official legal source URLs/access methods, extended terminology table (12 concepts x 6 countries), payment methods per country with dominant local options, data privacy laws with EU adequacy status, competitor presence mapping per country, distribution partner candidates (12 targets across 5 countries + global). **Key competitive intelligence discovered**:
  • BREAKING: Maite.ai acquired by Doctrine (France) for 10M EUR, backed by Summit Partners (~108M EUR)
  • Juztina (Peru): 100K+ users, fastest-growing LATAM legal AI, expanding to Colombia/Mexico/Argentina
  • Help AI (Mexico): $25 USD/mo, 30+ AI agents, direct competitor for Mexico entry
  • +2 more
AñadidoYouTube Knowledge Base Expansion (Wave 2)
  • Fixed 4 bugs in CodeLabs Hub backend: SSL cert handling, polymorphic context columns, pool stale reference, job queue concurrency
  • First wave: 54 videos → 58 items with transcripts → 1,159 vector chunks → 561,839 words indexed
  • Second wave: 290 new videos found via yt-dlp search (42 queries), 253 accepted for processing **Deep Transcript Analysis (4 parallel agents)**:
  • `docs/ENGLISH_LEGAL_AI_TRANSCRIPT_ANALYSIS.md` — Harvey, Spellbook, Eve Legal, CoCounsel, Robin AI, Paxton AI, etc.
  • `docs/LEGAL_AI_WORKFLOW_ANALYSIS.md` — 12 workflows, 15 anti-hallucination techniques, 12 verbatim prompts, model comparisons
  • `docs/PROCEDURAL_LAW_ANALYSIS.md` — 11-phase procedural map with LEC citations, 6 procedure checklists, 15 prioritized features **Key strategic findings**:
  • Lexiel 29€/mo vs Maite 80€+IVA vs CoCounsel $225/mo — massive price advantage
  • 3 unique features: deadline calculator, Vista Contraria, client portal
  • +8 more
AñadidoYouTube Intelligence & Platform Improvement Proposals
  • Analyzed 150+ YouTube videos across 10 categories (AI legal tools, Spanish procedural law, competitor demos, workflows)
  • Selected 55 priority videos for knowledge base indexing across 4 phases
  • Mapped entire Spanish legal AI YouTube ecosystem: competitor channels, content creators (S/A/B tiers), international channels
  • Created `docs/YOUTUBE_INTELLIGENCE_ANALYSIS.md` — comprehensive research document **Indexing Infrastructure**:
  • Created `scripts/index-youtube-videos.sh` — indexes 55 videos via CodeLabs Hub API (yt-dlp → VTT → Gemini embeddings → pgvector)
  • 4 phases: Competitor Demos (15), IA+Legal Workflows (15), Spanish Procedural Law (15), Strategy & Market (10)
  • Rate-limited (2s between requests), configurable via env vars (TOKEN, CONTEXT_ID, CONTEXT_TYPE) **Platform Improvement Proposals**:
  • Created `docs/PLATFORM_IMPROVEMENT_PROPOSALS.md` — gaps, quick wins, killer features, UX, marketing, pricing, 4-sprint roadmap
  • +3 more
CambioPricing Restructure + Vista Contraria Rename
  • Abogado: 49€/yr → **29€/yr** (39€/mo) — anchors at general AI price point (ChatGPT Plus level)
  • Despacho: 99€/user/yr → **79€/user/yr** (99€/mo) — 32% below Aranzadi One (117€), competitive with Prudencia.ai
  • Despacho tier gains: Calculadora de plazos procesales + Análisis de contratos IA
  • Previous pricing created a 2×Abogado (98€) ≈ Despacho (99€) anti-pattern — now resolved **Rename**: "Abogado del Diablo" → "Vista Contraria" (ES) / "Adversarial Analysis" (EN)
  • URL `/devils-advocate` and API `/v1/devils-advocate` unchanged (no broken links)
Añadido7 Killer Features Sprint
  • Feature 1: Voice Dictation → Structured Legal Document
  • `POST /v1/transcribe/structured` — Groq Whisper → Claude structuring pipeline via SSE
  • Standalone `/voice-dictation` page with recorder + structured output
  • Feature 2: Client Communication AI
  • `POST /v1/cases/:id/draft-communication` — Generates professional client emails/letters from case context
  • Side-by-side UI with form + draft preview
  • Feature 3: Contract Analysis with Spanish Legal Context
  • `POST /v1/contracts/analyze` — Clause-by-clause analysis with RAG-grounded Spanish legal references
  • +21 more
AñadidoSpanish Procedural Deadline Calculator (Killer Feature)
  • Full Spanish procedural deadline calculation engine covering 5 jurisdictions
  • 25+ procedure types: ordinario civil, verbal, monitorio, cambiario, ejecucion, divorcio, penal abreviado, juicio rapido, jurado, despido, cantidad laboral, seguridad social, contencioso ordinario/abreviado, concurso
  • Correct dia habil counting (excludes weekends, national holidays, Dec 24/31)
  • August suspension (Art. 183 LOPJ) — auto-applied for civil/admin, NOT for penal/social
  • Easter date calculation for movable holidays (Jueves/Viernes Santo)
  • Legal basis citations for every deadline (Art. X of LEC/LECrim/LJCA/LRJS/ET) **API**: `POST /v1/deadlines/calculate` + `POST /v1/deadlines/bulk` **UI**: 5-step wizard integrated into deadlines view with "Calcular plazos" button **Competitive Intelligence**: `docs/COMPETITIVE_DEEP_ANALYSIS.md` — 550+ lines, 90+ sources, covering Harvey AI, Vincent/vLex, 7 Spanish competitors, gap analysis, partner program design, content creator database
AñadidoCase-Centric Restructuring (Post-MVP: Phases 4-10)
  • Phase 4 — Enhanced Anonymization UX:
  • `PrivacyBadge` component — green shield badge with entity count, compact/full modes, tooltip about European servers
  • `EntityConfirmation` component — interactive entity review with color-highlighted text, checkbox toggles, manual entity addition, anonymized preview toggle
  • Integrated into case workspace layout header and case-chat-view
  • Phase 5 — Dashboard Redesign:
  • Rewrote dashboard page: greeting bar, StatPill stats (active cases, pending deadlines, docs), urgency alerts
  • 3 quick action cards: Nuevo Procedimiento (primary CTA), Consulta Rapida, Escanear Documento
  • Active cases grid (3-col) with status badges, practice area, party names, empty state
  • +29 more
AñadidoCase-Centric Restructuring (MVP Sprint: Phases 1-3)
  • Database & Schema:
  • New `cases` table with 17 columns (title, caseNumber, courtCaseNumber, status, role, practiceArea, jurisdiction, parties JSONB, factsSummary, caseBrief JSONB, anonymizationEnabled)
  • New `case_activities` table for timeline tracking (10 activity types)
  • 4 new enums: `caseStatusEnum`, `caseRoleEnum`, `practiceAreaEnum`, `caseActivityTypeEnum`
  • Added nullable `caseId` FK to conversations, documents, deadlines tables (backward compatible)
  • 12 indexes on cases/activities for performant queries
  • Migration generated: `0002_needy_chimera.sql`
  • API Routes:
  • +30 more
CorrecciónProduction Audit Fixes
  • Settings HTTP method bug — `handleSaveProfile` was using `api.post('/v1/profile', { method: 'PATCH', ... })`, sending "PATCH" as a JSON body field instead of as the HTTP method. Changed to `api.patch('/v1/profile', { ... })`. Organization update also changed from raw `fetch` to `api.patch`.
  • Settings silent errors — Replaced 3x `console.error` with `toast()` calls for profile save, checkout, and billing portal errors. User now sees feedback on failure.
  • Chat stale closure fix — `handleSend` now captures `activeId` at call time and compares against `activeIdRef` before updating messages/streaming content. Prevents assistant response from old conversation being appended to newly-selected conversation's message list.
  • Sidebar collapsed logout safety — Collapsed avatar no longer directly triggers `logout()`. Now shows separate LogOut icon with `window.confirm()` dialog. Added `aria-label` to dark mode toggle and logout buttons.
CorrecciónGap Closure (Broken Wiring & Missing UI)
  • Chat follow-up suggestions wired — `chat-view.tsx` now extracts `metadata.suggestions` from SSE `done` event and passes `suggestions` + `onSuggestionClick` props to `ChatMessages`. Clears suggestions on new send. Full pipeline: system prompt → backend parse → SSE → frontend pills.
  • Conversation PDF export button — Download button (Download icon) in chat top bar. Fetches `GET /v1/conversations/:id/export` with auth header, creates blob URL for download.
  • SSE heartbeat keepalive — New `sse-heartbeat.ts` utility sends `event: ping` every 15 seconds during streaming. Applied to all 8 SSE endpoints across 7 route files. Prevents reverse proxy timeout on long LLM calls (30-60s before first token). Cleanup via `finally` block.
  • Saved sources view — "Guardados" tab in jurisprudence view with Search/Saved tab switcher. Lists all saved sources with type/jurisdiction badges, summary preview, date, and delete button (Trash2). Calls `GET /v1/sources/saved` and `DELETE /v1/sources/saved/:id`. Empty state with guidance.
AñadidoStripe Live Mode & Email Configuration
  • STRIPE_MODE flag system — Dual test/live mode via `STRIPE_MODE` env var. `stripe.ts` resolves prefixed keys (`STRIPE_LIVE_*` or `STRIPE_TEST_*`) with fallback to unprefixed. Supports hot-switching without code changes.
  • Stripe live mode activated — KYC completed, live account with business name "Lexiel", URL lexiel.ai, statement descriptor "LEXIEL AI". All live keys (secret, publishable, webhook secret, 4 price IDs) configured in `.env` and Railway.
  • Railway env var fix — `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` on lexiel-app was `pk_test_*` while API was in live mode. Updated to `pk_live_*` to prevent checkout session mismatch (frontend Stripe.js must match backend mode).
  • Stripe email notifications — Configured via Stripe Dashboard: - Default email language: Espanol (Espana) - Payment success + refund emails enabled - 5 subscription notification types: trial ending, renewals, expiring cards, failed card/bank payments - Payment method updates: migrated from legacy mixed-link to Stripe-hosted page (irreversible) - Subscription management link enabled (Stripe customer portal) - Invoice finalization + credit note emails enabled - Invoice reminder emails enabled (on due date)
  • Stripe account verified — Business name "Lexiel" (was already correct, not "TubExperto sandbox"), owner Jose Diaz Moreno, support contact configured.
AñadidoWave 7: Wow Polish (Full UI Refinement)
  • XSS fix in Declarations print — Added `escapeHtml()` to sanitize user-provided values (targetName, witnessName, question text) in print layout innerHTML.
  • Chat stop button — Red stop button (Square icon) replaces send button during streaming. AbortController wired into fetch; on abort, partial content preserved as message with "[Respuesta interrumpida]".
  • Chat deep-links — `useSearchParams` reads `?id=` param to auto-select conversations on navigation. Dashboard links now use `?id=` for conversations and documents.
  • Dashboard polish — Urgency alert banner for overdue deadlines (animated pulse), hover lift animations on action cards, ChevronRight indicators on recent items, deep-link fixes.
  • Composer progress — Replaced fake progress bar with real section-based progress (done/total + percentage). Unicode status dots replaced with SVG icons. Word counts on edit textarea and facts textarea.
  • Deadlines polish — Stat cards are now clickable filter buttons with active state visual. Overdue icon gets pulse animation. DaysIndicator shows red pulsing dot for overdue/today. Form has slide-in animation.
  • Markdown copy button — `CodeBlock` component with copy-to-clipboard button (appears on hover). Custom scrollbar styling (webkit + Firefox). Focus-visible accessibility outlines.
  • Writing view icons — Distinct colored icons per document type (Gavel/Shield/ArrowUpRight/Handshake/FileText/BookOpen/Search). Completed step indicators show green checkmarks.
  • +5 more
AñadidoPremium Gap Closure (Wow Polish)
  • Chat cross-feature buttons — "Enviar al Compositor" and "Crear plazo" buttons in assistant message toolbar. Context passed via sessionStorage for seamless navigation to Composer/Deadlines with pre-filled data.
  • Jurisprudence structured case cards — AI output parsed into typed cards (sentencia/ley/doctrina) with expandable details, CENDOJ/BOE search links, count badges per type, and cards/text view toggle.
  • Devil's Advocate weakness cards — Severity-rated cards (CRITICO/MEDIO/BAJO) with color-coded dots, expandable descriptions, legal basis references, and per-weakness "Aplicar mejora en Compositor" button for targeted fixes.
  • Declarations print layout — Professional court-ready print document: header with title/date, meta row (jurisdiction, target/witness), numbered question cards with strategy annotations, full markdown analysis, and Lexiel-branded footer. Page-break-safe per question.
AñadidoPremium Upgrade: DB-Dependent Features
  • Save to Collection — Jurisprudence search results can be bookmarked. New `saved_sources` table, CRUD API endpoints (`POST/GET/DELETE /v1/sources/saved`), bookmark button with saved/saving states in jurisprudence view. Unique constraint prevents duplicates.
  • Save as Template — "Plantilla" button in Composer toolbar saves generated documents as reusable templates via existing `POST /v1/templates` endpoint.
  • Template Learning (Style Capture) — When lawyers edit AI-generated sections in the Composer, diffs are analyzed by LLM to extract style preferences (tone, formality, vocabulary, citation format, structural notes). Stored per organization + document type in `style_preferences` table. Preferences auto-injected into future composition prompts for the "writes like our senior partner" effect. Fire-and-forget learning — no UI friction.
  • DB Driver Migration — Switched from Neon HTTP driver (`@neondatabase/serverless`) to postgres.js for compatibility with self-hosted PostgreSQL at db.codelabs.studio.
AñadidoPremium Upgrade Gap Closure
  • Chat RAG source panel — Collapsible "Fuentes utilizadas" panel per message showing source title, type badge, relevance score, and official reference. Backend passes RAG retrieval results in SSE done event metadata.
  • Devil's Advocate → Compositor — "Mejorar en Compositor" button sends defense strategy and recommendations to Legal Composer via sessionStorage, pre-filling facts with the analysis.
  • Declarations structured cards — Interrogatory results parsed into numbered question cards with expandable strategy (objective, if-yes/no, warnings). Cards appear above full markdown output.
  • Cross-feature context passing — All document analysis buttons (Chat, Writing, Deadlines) now pass document summary, findings, and entities via sessionStorage. Target pages pre-fill forms with context.
AñadidoPremium Upgrade (Full Feature Enhancement)
  • Wave 1: Infrastructure
  • Markdown Rendering — Shared `<MarkdownRenderer>` component (react-markdown + remark-gfm) replaces all `whitespace-pre-wrap` divs across 6 features. Streaming cursor animation, legal prose typography.
  • Streaming Hook — `useStream()` hook with AbortController, 2-min timeout, exponential retry (2x), rAF-debounced state updates. Eliminates 6 copy-pasted SSE parsers.
  • Toast System — Lightweight notification system (React context + portal). Replaces invisible `console.error` calls with user-visible feedback.
  • Wave 2: Feature Deepening
  • Chat — RAG source panel ("Fuentes utilizadas"), AI-generated follow-up suggestions (server-side `[SUGERENCIAS]` parsing), per-message action toolbar (copy, regenerate), conversation PDF export, markdown-rendered messages.
  • Legal Composer — Inline section editing (preview/edit toggle per section), real citation verification against backend `/v1/verify` endpoint.
  • Documents — Document comparison tab with AI-powered diff analysis (clauses, risks, obligations, deadlines), cross-feature buttons with context passing via sessionStorage ("Discutir en Chat", "Redactar escrito", "Crear plazos"), drag-and-drop file upload, case timeline for extracted dates.
  • +12 more
AñadidoBeta Feedback Widget
  • Feedback system — Thread-based feedback channel for beta testers. Users can send text messages and voice notes (recorded via MediaRecorder, transcribed by Groq Whisper). Admins can view all threads, reply, and manage status (open/resolved/archived).
  • Database — New `feedbackThreads` and `feedbackMessages` tables with `feedback_status` and `feedback_sender_role` enums. `isBetaTester` boolean flag on `users` table.
  • API endpoints — User routes: CRUD threads/messages + audio upload with Groq Whisper transcription. Admin routes: list all threads, reply, update status, unread count. Beta tester guard middleware.
  • Feedback widget — Floating bubble (bottom-right) with unread badge, opens chat panel with thread list/detail views. Self-gates via provider — renders nothing for non-beta users.
  • Admin panel — Split layout at `/admin/feedback` with thread list (filterable by status), message view, and reply input. Sidebar link with live unread count badge.
  • Analytics — `trackFeedbackSubmit('text' | 'voice')` Plausible event.
Corrección
  • ESM hoisting in storage.ts — `const isProduction` was evaluated at module load time, before dotenv ran (ESM hoists all imports). Changed to lazy `useR2()` function that checks `process.env.R2_ACCESS_KEY_ID` at call time. Audio and documents now correctly route to R2 when credentials exist.
  • Presigned URLs on POST responses — `POST /v1/feedback`, `POST /v1/feedback/:threadId/messages`, and `POST /v1/admin/feedback/:threadId/messages` now resolve R2 presigned URLs before returning, so the frontend can play audio immediately without re-fetching.
  • Duplicate .env entries — `R2_ACCESS_KEY_ID` and `R2_SECRET_ACCESS_KEY` appeared twice in `.env` (empty first, real second). Dotenv uses first occurrence, so R2 was never activated. Consolidated to single entries.
  • AudioPlayer play state — `audio.play()` returns a Promise; the toggle was setting state optimistically. Now waits for the Promise to resolve before updating UI.
  • FeedbackInput stale closure — `recordingDuration` in `startRecording` deps caused the callback to recreate every second during recording. Moved to a ref for the recording callback, state for display only.
AñadidoDevil's Advocate & Declaration Prep
  • Devil's Advocate — `POST /v1/devils-advocate/analyze` takes a legal brief and tears it apart from the opponent's perspective. Uses Claude Opus for deep adversarial analysis across 6 dimensions: critical vulnerabilities, legal counterarguments, evidence gaps, procedural errors, defense strategy, and global risk score (1-10). Supports jurisdiction selection, role (demandante/demandado), and optional focus areas.
  • Declaration Preparation — Two endpoints: `POST /v1/declarations/interrogatory` generates 15-30 strategic questions with objectives, follow-ups, and document confrontation strategy for parties, witnesses, or experts. `POST /v1/declarations/witness-prep` generates witness preparation guides with questions we'll ask, anticipated opposing questions, and cross-exam simulations.
  • Dashboard UI — `DevilsAdvocateView` (red-themed, adversarial) and `DeclarationsView` (dual-mode: interrogatory + witness prep) with SSE streaming, copy functionality, and multi-step forms.
  • Sidebar navigation — Added Swords icon (Vista Contraria) and UserCheck icon (Declaraciones) between Composer and Deadlines.
AñadidoPhase 7: Team Invitations & Deadline Reminders
  • Team invitation system — Email-based org invitations: `POST /v1/team/invite` creates a 7-day secure token (256-bit hex), sends branded HTML invitation email via Resend. Invites are stored in new `invitations` DB table.
  • Invitation accept page — `/[locale]/invite/[token]` page lives outside auth guard. Shows invitation details, handles expired/already-accepted states. If not logged in, shows Keycloak login + register CTAs with `redirect_uri` back to accept page. Auto-accepts if logged in with matching email.
  • Email-match enforcement — Accept endpoint (`POST /v1/team/invitations/:token/accept`) rejects if authenticated user's email differs from invited email (`EMAIL_MISMATCH`). Updates user's `organizationId` and `role` on accept.
  • Team management UI — `TeamView` component: lists current members with role badges (owner/admin/member), invite form, pending invitations panel with cancel buttons. Permission-gated (owner/admin only for mutations).
  • Deadline email reminders — Daily in-process scheduler (`setInterval(24h)`) queries deadlines due tomorrow, groups by owner, sends amber-alert HTML email via Resend. Requires `RESEND_API_KEY` — silently skips if not set.
  • invitations DB table — Drizzle schema: id, organizationId (FK cascade), invitedBy (FK cascade), email, role, token (unique index), expiresAt, acceptedAt, createdAt. Pushed to Neon.
  • Equipo/Team sidebar link — Added to sidebar nav with Users icon (between Deadlines and Settings).
  • Branded email templates — `sendInvitationEmail()` and `sendDeadlineReminderEmail()` HTML templates added to email.ts.
InfrastructureStripe Test Mode on Railway
  • Stripe env vars configured — 6 env vars set on Railway lexiel-api service: STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, and 4 price IDs (Starter monthly/yearly, Professional monthly/yearly). Using dedicated Lexiel Stripe account (dedicated account), not shared TubExperto.
  • Billing flow unblocked — Checkout, subscription management, and webhook handling now functional in test mode on production deployment.
Corrección
  • i18n: 10 hardcoded strings moved to dictionary — hero.tsx trust badges (RGPD/GDPR, Servidores EU/EU Servers, Anti-alucinación/Anti-hallucination), features.tsx "Ver más"/"Read more", comparison.tsx legend (Sí/Yes, Parcialmente/Partially, No disponible/Not available), comparison subtitle "Legal AI"/"IA Legal".
  • Removed inline locale ternaries — comparison.tsx used `dict.meta.locale === 'es' ? ...` pattern, replaced with proper dict.comparison.* keys.
Dependencies
  • serialize-javascript RCE fix — Override `serialize-javascript@7.0.3` (was 6.0.2) via npm overrides. Fixes GHSA-5c6j-r48x-rmvq RCE through Sentry → webpack → terser-webpack-plugin chain
  • fast-xml-parser stack overflow fix — Override `fast-xml-parser@5.4.1` (was 5.3.6) via npm overrides. Fixes stack overflow DoS through @aws-sdk/client-s3 chain
  • Vulnerability reduction — 29 vulnerabilities → 4 (all moderate, esbuild dev server only — not exploitable in drizzle-kit context)
PerformanceServer Component Optimization
  • 11 components converted to server components — Removed unnecessary `'use client'` from features, how-it-works, social-proof, security, comparison, verification, testimonials, cta-section, footer, floating-widgets, glow-button. These are purely presentational and can render client children (AnimatedSection) without being client components themselves
  • tesseract.js lazy import — Changed from top-level `import` to dynamic `await import('tesseract.js')` in OCR module, deferring ~7MB from API cold start
AnalyticsSelf-Hosted Plausible CE
  • Plausible CE deployed — Self-hosted Plausible Community Edition v2.1 on Hetzner VPS at analytics.codelabs.studio. Cookie-free, RGPD-compliant analytics with zero third-party data sharing.
  • 8 domains registered — lexiel.ai, app.lexiel.ai, codelabs.studio, tubexperto.com, docu.expert, flirtmaster.app, mytradingai.expert, dancesteps.app
  • Lexiel integration — Tracking script added to both marketing site (lexiel.ai) and app dashboard (app.lexiel.ai) with CSP headers updated
  • docu.expert migration — Replaced Google Analytics 4 with Plausible. Removed GA4 scripts, GTM, and Google analytics CSP entries
  • tubexperto migration — Replaced GoogleAnalytics component and gtag-based event tracking with Plausible's window.plausible() API. All custom events preserved with same names
  • Privacy improvement — ~1KB script (vs GA4's ~28KB), no cookies, no consent banner needed for analytics
SeguridadPost-Audit P1/P2 Fixes
  • JWT httpOnly cookies — Auth middleware now reads cookie `lexiel_token` as fallback to Bearer header. Login/dev-login endpoints set cookie (httpOnly, Secure in prod, SameSite=Lax, maxAge=24h). Auth context removes localStorage entirely — token is in-memory only, rehydrated from cookie on page reload.
  • Presigned URLs for documents — `GET /v1/documents/:id/download` returns a 15-minute signed URL for private R2 objects. Documents are never publicly accessible.
InfrastructureAgenteUno Agent + R2
  • Agent Lex created — Real AgenteUno agent `73e8d71c-ac6c-45cc-9bed-bd37e3f5caea` with WebChat channel `07045d99-ff66-48a7-afb0-6f7582666388`. Lexiel KB re-linked to real agent ID. Env vars set in Railway lexiel-web.
  • R2 presigner — `@aws-sdk/s3-request-presigner` installed. `getPresignedUrl()` exported from `storage.ts`. Shared S3 client factory eliminates code duplication.
  • Stripe publishable key — `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` added to Railway lexiel-app.
  • GIN trigram indexes — `pg_trgm` extension enabled on Neon. `legal_sources_title_trgm_idx` and `legal_sources_fulltext_trgm_idx` created for fast ILIKE/similarity search on legal corpus.
SeguridadFull Audit Remediation
  • CSP + HSTS headers — Added Content-Security-Policy, Strict-Transport-Security, and Permissions-Policy to both `apps/web` and `apps/app` via `next.config.ts` headers
  • Health endpoint hardened — Removed API key configuration leak (was exposing ANTHROPIC_API_KEY, GROQ_API_KEY, GOOGLE_GEMINI_API_KEY presence/absence)
  • Dev-login conditional registration — Changed from runtime guard to conditional route registration (`if (NODE_ENV !== 'production')` wraps entire route definition)
  • prettyJSON gated — `prettyJSON()` middleware now only active in non-production environments
Performance
  • Font loading — Added Inter via `next/font/google` with `display: 'swap'` and CSS variable `--font-inter` on marketing site
  • Dynamic imports — FloatingWidgets, ChatbotWidget, CookieConsent now loaded with `next/dynamic` (`ssr: false`) to reduce initial bundle
  • Resource hints — Added `preconnect` and `dns-prefetch` for api.lexiel.ai and auth.codelabs.studio
Accessibility
  • Skip-to-main links — Added as first focusable element on both marketing site and app dashboard layouts
  • Keyboard accessibility — Converted `<div onClick>` to `<button>` in chat sidebar (conversation items), documents view (expandable rows), and chatbot widget
  • Form labels — Associated all login form labels with inputs via `htmlFor`/`id` pairs, added `autoComplete` attributes
  • ARIA attributes — Added `aria-label` to icon-only buttons (delete, clear, close, send), `aria-hidden` to decorative SVGs and backdrop overlays, `aria-expanded` to expandable document rows
SEO
  • Hardcoded strings localized — Moved 8 hardcoded Spanish strings to i18n dictionaries with EN translations: `trustStrip`, `tags.mostUsed/antiHallucination/new`, `billedAnnually`, `demoTitle/demoVerified/demoProgress`
  • Blog images optimized — Replaced raw `<img>` tags with Next.js `<Image>` component for hero and related post images on blog post pages
Dependencies
  • drizzle-kit updated 0.30.6 → 0.31.9 (fixes esbuild vulnerability)
  • drizzle-orm updated 0.38.4 → 0.45.1
  • Removed unused: `clsx` from apps/web, `ws` + `@types/ws` from packages/api
Email
  • DKIM key generated — 2048-bit RSA key generated via rspamd on Mailcow server for lexiel.ai, DNS record updated in Cloudflare
  • SPF hardened — Changed from `~all` (softfail) to `-all` (hardfail), replaced broken `include:mail.codelabs.studio` with `ip4:46.62.213.212`
DeployedProduction Launch
  • All 3 services live with SSL — lexiel.ai (web), app.lexiel.ai (dashboard), api.lexiel.ai (API). Custom domains verified, Let's Encrypt certs issued by Railway.
  • CTA URLs fixed — All buttons now point to `https://app.lexiel.ai/signup` (was `localhost:4967`). Fixed via Docker `ARG`/`ENV` for `NEXT_PUBLIC_*` build-time injection.
  • Railway domain verification — Added `_railway-verify.app` TXT record for `app.lexiel.ai` cert provisioning. DNS verified, cert status: VALID.
  • Lighthouse scores — Performance: 97, Accessibility: 95, Best Practices: 100, SEO: 100
  • API health — `/health` avg 277ms, `/v1/models` avg 320ms (both under 500ms target). DB latency ~500ms (Neon cold start).
AñadidoInfrastructure & RAG
  • RAG corpus indexed — 67 chunks across 7 core Spanish laws (CC, CE, LEC, ET, LOPDGDD, CP, LGT) indexed with `gemini-embedding-001` (3072 dims). Semantic search now functional.
  • Embedding model upgraded — Migrated from 768-dim to 3072-dim vectors (`gemini-embedding-001`). Schema updated in `legal_source_embeddings` and `document_chunks`. DB schema pushed via drizzle-kit.
  • Resend email verified — `lexiel.ai` domain verified in Resend (DKIM + SPF + DMARC). Email from `hola@lexiel.ai` now deliverable.
  • Chatbot widget fixed — Uses `NEXT_PUBLIC_AGENTEUNO_AGENT_ID` env var; gracefully returns null if not configured (no broken widget on marketing site).
  • R2 bucket created — `lexiel` bucket in Cloudflare R2 (WEUR region). Storage ready for document uploads.
  • Railway API vars synced — `ANTHROPIC_API_KEY`, `GROQ_API_KEY`, `GOOGLE_GEMINI_API_KEY`, `RESEND_API_KEY`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`, `APP_URL` now set in Railway `lexiel-api` service.
  • services.json updated — Lexiel project fully documented with all API keys, services, and pending items.
Añadido
  • AgenteUno Onboarding Checklist — Lexiel (first real customer) now visible in AgenteUno dashboard with 4/6 onboarding tasks complete: agent ✅, knowledge base ✅, web chat ✅, WhatsApp ✅. Two document upload tasks remaining (ID + business verification for Telnyx).
  • Agent Lex Knowledge Base — KB populated in AgenteUno with 7 product documents (Qué es Lexiel, Características, Cómo funciona, Precios, Seguridad, Verificación, FAQ). 25 chunks with BAAI/bge-small-en-v1.5 384-dim embeddings. Agent Lex can now answer product questions accurately.
  • AgenteUno platform improvements (implemented upstream, benefits Lexiel): - `onboarding_tasks` jsonb persisted per organization - `PATCH /v1/organizations/onboarding-tasks` for marking document uploads complete - `POST /v1/organizations/documents/upload-url` for presigned R2 document upload - `auth/me` now returns `onboardingTasks` in organization object - `POST /v1/knowledge/:id/reprocess` to re-trigger embedding pipeline
Seguridad
  • CRITICAL: Admin cross-tenant data leak fixed — Admin routes (`/v1/admin/stats`, `/v1/admin/users`) now scoped to the requesting user's organization. Previously, any `owner` could enumerate all users/orgs/conversations across the entire platform.
  • Rate limiter hardened — Extracts first IP from `X-Forwarded-For` (prevents header spoofing bypass), adds periodic cleanup of expired buckets (prevents memory leak)
  • Gemini API key moved to header — API key sent via `x-goog-api-key` header instead of URL query parameter (prevents key exposure in logs)
  • Dev-login runtime guard — Returns 404 in production even if route is registered; token expiration reduced from 7d to 24h
  • JWT algorithm restriction — Verification explicitly restricted to HS256 (defense-in-depth against algorithm confusion)
  • CORS wildcard matching fix — Removed overly permissive suffix check that could match `evil-lexiel.ai` as a subdomain of `lexiel.ai`
  • Anonymize endpoint Zod validation — Replaced raw `c.req.json()` with `zValidator` for consistent input validation
  • Missing env var documented — Added `GOOGLE_GEMINI_API_KEY` to `.env.example`
Añadido
  • Sentry error tracking — Integrated across all 3 services: `@sentry/node` for API (error handler captures 5xx), `@sentry/nextjs` for Web + App (instrumentation.ts, client/server/edge configs, withSentryConfig wrapper). Conditional on `SENTRY_DSN`/`NEXT_PUBLIC_SENTRY_DSN` — no-op when unconfigured.
  • Cookie consent banner — RGPD-compliant banner with accept/reject options, bilingual ES/EN, localStorage persistence, dismiss via X button, links to /cookies page
  • Terms of Service acceptance flow — `tosAcceptedAt` column in users table, checkbox in onboarding overlay (links to /terminos + /privacidad), profile API supports `acceptTos`, button disabled until accepted
  • Contact page — Full contact form (name, email, company, subject selector, message) with mailto submission, contact info sidebar (email + response time), demo CTA card. Bilingual ES/EN via dictionary. Dedicated `ContactPageContent` component with `'contact'` page type in catch-all router.
  • DPA page — RGPD Art. 28 Data Processing Agreement, bilingual ES/EN, 10 sections (object, definitions, purpose, obligations, breach notification, sub-processors table, international transfers, audit rights, amendments, contact). Route: /es/acuerdo-tratamiento-datos, /en/data-processing-agreement
  • Database health monitoring — Health endpoint now pings DB with `SELECT 1`, measures latency in ms, returns 503 when degraded
  • LLM error alerting — All Claude/Groq failure points in ai.ts now report to Sentry with provider and action tags
Corrección
  • Legal pages email inconsistency — Standardized all legal page contact emails from `jose@lexiel.ai` to `hola@lexiel.ai` (matching footer and floating widget)
  • Cookie policy Plausible listing — Removed false `_plausible` cookie entry; Plausible Analytics is cookie-free by design. Updated both ES/EN versions.
  • Tax specialty typo — Fixed "dificultiades" to "dificiles" in specialties.ts
  • Info pages accent inconsistency — Removed stray accent on "genericas" for consistency with ASCII-only content
  • DPA page syntax error — Fixed DPA content that was placed outside the `legalContent` object literal, causing TypeScript compilation failure
  • Footer contact link — Changed hardcoded `/contact` to `getLocalizedPath('contact', locale)` for proper localized URLs (/es/contacto, /en/contact)
  • Duplicate contact route — Deleted fake contact form at `/[locale]/contact/page.tsx` (simulated submission, never sent) and `contact-form.tsx`. EN users now correctly reach the functional `ContactPageContent` via catch-all router.
  • Admin API routes secured — Added `x-admin-secret` header auth to heroes upload/delete/status endpoints (previously completely unauthenticated)
  • +8 more
Cambio
  • App dashboard performance — Added AVIF/WebP image formats, optimizePackageImports for lucide-react, immutable caching headers for static assets
  • API caching — `/v1/models` endpoint now returns `Cache-Control: public, max-age=3600`
  • JWT secret hardened — Railway production JWT_SECRET replaced from dev placeholder to 48-byte random secret
  • IVA tax rate — Created 21% Spanish IVA tax rate in Stripe (txr_1T5pIyAMs2yPs0FL1XhkKS92)
  • Testimonials section — 3 Spanish lawyer testimonials on landing page (between Security and Pricing), staggered animation, bilingual ES/EN
  • Inline verification badges — Citation references highlighted within chat message text with confidence-colored badges (green/amber/red), collapsible citation summary panel replacing static block
  • BOE API client (packages/api/src/lib/boe.ts) — Full integration with Spain's Boletin Oficial del Estado open data API: search consolidated legislation, fetch metadata/full text/block text, daily summaries, 20+ core law identifiers (CC, CE, LEC, CP, ET, LOPDGDD, etc.)
  • CENDOJ scraper (packages/api/src/lib/cendoj.ts) — Session-managed HTML scraper for CGPJ court decision search: jurisdiction filtering, ECLI/ROJ lookup, result parsing
  • +4 more
Cambio
  • Stripe pricing alignment — Products renamed to "Lexiel Abogado" / "Lexiel Despacho", new prices created (Abogado: 65€/mo, 588€/yr; Despacho: 129€/mo, 1188€/yr), old prices deactivated
  • PLAN_LIMITS updated — Starter: 200 queries/mo, 30 docs/mo; Professional: unlimited queries + documents, 25 users
  • Citation data enriched — Backend now sends `confidence` (high/medium/low/unverifiable) and `type` (ley/sentencia/articulo/etc.) fields to frontend
  • Stripe customer portal — Configured with subscription cancellation (with proration), plan switching (Abogado/Despacho), payment methods, address + tax_id fields, cancellation reasons
Corrección
  • Sentry build crash — Removed `withSentryConfig` wrapper from both `next.config.ts` files. `@sentry/nextjs@10.40.0` webpack plugin generates broken `vendor-chunks/@opentelemetry.js` in Next.js 15 App Router, crashing during "Collecting page data". Runtime Sentry (instrumentation.ts) still works. Also fixed `hideSourceMaps` → `sourcemaps.deleteSourcemapsAfterUpload` type error.
  • Docker standalone mode restored — Re-added `output: 'standalone'` + `outputFileTracingRoot` to both Next.js configs (Dockerfiles depend on standalone output). The original `_document.js` error was caused by `withSentryConfig`, not standalone mode.
  • Docker workspace fix — All Dockerfiles now copy ALL 5 workspace package.json files (npm ci requires them even when building a single package)
  • Docker NEXT_PUBLIC build args — Added `ARG NEXT_PUBLIC_APP_URL`, `ARG NEXT_PUBLIC_SENTRY_DSN`, etc. to Dockerfiles. Railway injects env vars at runtime only; Next.js needs them at build time for inlining.
  • Production localhost URLs — CTA buttons pointed to `localhost:4967` because NEXT_PUBLIC_APP_URL wasn't available during Docker build. Fixed with ARG/ENV in Dockerfiles.
  • Sentry DSN configured — Created 3 Sentry projects (lexiel-api, lexiel-web, lexiel-app) under org `agenteuno`, team `lexiel`. DSN values set in Railway env vars for all services.
  • Neon PITR extended — Database backup retention increased from 1 day to 7 days (max for Launch plan)
  • index-all-sources.ts — Migrated from OpenAI SDK to Gemini REST API for embeddings (was broken after removing openai dep)
  • +2 more
Añadido
  • Stripe customer portal — Configured via API: payment methods, subscription cancel/update, invoice history, both products with all prices (bpc_1T5ofeAMs2yPs0FLF5nDJOJ5)
  • Drizzle migration — Generated full schema migration (0000_overconfident_mauler.sql), pushed to Neon DB with `drizzle-kit push`
  • Verification badges in chat — Citation badges now render in the live chat page (`page.tsx`) — previously only worked in `chat-view.tsx`. Fixed missing `citations` field in message builder.
  • Onboarding overlay — New users shown profile completion modal after first login (collects despacho name, bar association, license number, specialization). Uses `barAssociation` null-check as completion signal. Dismissible with "Completar más tarde".
  • Email transaccional — Resend integration (`packages/api/src/lib/email.ts`). Welcome email sent automatically on first registration. HTML template with feature overview and CTA.
  • Dark sidebar — AppSidebar redesigned with `#0D0F14` background, white/translucent text, matching the ProductDemo aesthetic. Uses `logo-white.svg`.
  • Admin panel — `/admin` route with: metric cards (total users, orgs, conversations, active users), usage by type bar chart, 14-day activity chart, users table with plan badges. Accessible only to `owner`/`admin` roles.
  • Floating widget — Replaced placeholder WhatsApp button (no number available) with email contact link to `hola@lexiel.ai`.
  • +2 more
Cambio
  • Pricing Overhaul v2 — Complete pricing restructure based on Spanish legal AI market analysis - Plans renamed: Starter→Abogado (49€/mo annual), Professional→Despacho (99€/user/mo annual), Enterprise unchanged - Monthly prices: Abogado 65€/mo, Despacho 129€/user/mo (25% annual discount) - Per-user pricing for Despacho plan (previously flat rate for 2-10 users) - Enhanced feature lists reflecting professional tool value - "Deducible como gasto profesional" badge, trial badge, strikethrough monthly price on annual - ROI messaging: "Se amortiza ahorrando 30 min al mes" - Pricing card hover effects (scale + shadow transition)
  • Unified CTA — All buttons changed from "Empieza gratis" to "Empezar ahora"
  • GlowButton shared component — Extracted to glow-button.tsx, constant rotating rainbow glow (no pulsing), used in hero, pricing, CTA section, and navbar
  • Navbar signup button — Now uses GlowButton with rainbow glow effect
  • Logo .ai suffix — Added ".ai" text after SVG logo in navbar
  • Hero secondary CTA — "Ver demo"→"Ver cómo funciona", links to #demo instead of #features
  • Interactive product demo — All 4 tabs now functional with distinct content: - Consulta: AI chat with typing animation and realistic legal conversation - Redactar: Document editor with section navigator, citations sidebar (unchanged from v1) - Jurisprudencia: Search results with relevance scores, tribunal info, filters - Expedientes: Case management table with deadline tracking, status badges
  • Floating widgets repositioned — Chatbot moved to bottom-right, contact widget above it
  • +2 more
Añadido
  • Landing Page Redesign v2 (arkangel.ai style) - Futuristic minimalism: white-dominant, Montserrat headings, single indigo accent (#4F46E5) - Product demo mockup (fake Lexiel chat UI with sidebar, messages, citations) - Stats/social proof bar (500+ docs analyzed, 99.2% precision, 7 legal codes, 24/7) - Clean navbar, pill-shaped nav, flag-based language switcher - Subtle framer-motion fade-in scroll animations only
  • i18n Localized URLs - Route slugs map (packages/shared/src/i18n/routes.ts): ES↔EN for all pages - Catch-all route (apps/web/app/[locale]/[...slug]/page.tsx) resolving slugs to components - Middleware support for localized slug redirections
  • Legal Pages (5 bilingual) - Privacy Policy, Terms of Service, Cookie Policy, Legal Notice, Data Protection - Content in apps/web/lib/legal.ts (~1200 lines Markdown) - Legal page renderer with ReactMarkdown, Table of Contents sidebar, last-updated date - RGPD compliant, EU AI Act aligned, subprocessor disclosure
  • Specialty Landing Pages (10 bilingual) - Civil, Penal, Laboral, Familia, Mercantil, Administrativo, Fiscal, Inmobiliario, Extranjeria, Bancario - Content in apps/web/lib/specialties.ts (~800 lines) - Each page: contextualized hero, problem, example queries, legal sources, CTA
  • Footer Redesign - 5-column layout: Product, Specialties (10 links), Resources, Company, Legal - Social links (X, LinkedIn, Instagram, Facebook, YouTube, Email) - Trust badges: "Servidores en la UE", "RGPD Compliant", "EU AI Act Ready"
  • Floating Widgets - WhatsApp, Instagram, Messenger buttons with staggered animation - Chatbot widget placeholder (AgenteUno integration ready)
  • Info Pages (About Us, FAQ, Documentation) — bilingual with localized URLs
  • OCR + Document Anonymization - Tesseract.js OCR for image-to-text (packages/api/src/lib/ocr.ts) - Scanned PDF fallback: pdf-parse text → OCR if text < 50 chars - Anonymization engine (packages/api/src/lib/anonymizer.ts): DNI/NIE/CIF/IBAN/phones/emails + names - API endpoints: POST /v1/documents/ocr, POST /v1/documents/anonymize - Documents UI: 3-tab layout (Documents / OCR / Anonymize) with entity stats and mapping table
  • +14 more
Corrección
  • conversations.ts: message type cast for Drizzle pgEnum compatibility
  • conversations.ts: verificationStatus 'partial' → 'partially_verified' matching DB enum
  • legal-search.ts: r.title → r.source.title (SearchResult type)
  • legal-writing.ts: Buffer → Uint8Array for Response body compatibility
  • llm.ts type errors: explicitly typed models array for getAvailableModels() to accept all tiers
  • ocr.ts pdf-parse import: changed to PDFParse class API matching documents.ts pattern
  • RAG Pipeline (pgvector + Legal Corpus) - pgvector extension enabled on Neon with HNSW indexes (cosine similarity) - Embedding columns: vector(768) on legal_source_embeddings and document_chunks (Gemini) - Gemini gemini-embedding-001 REST API for embedding generation (lib/embeddings.ts) - Text chunker with legal-aware splitting on article boundaries (lib/text-chunker.ts) - Indexer service for legal sources and documents (lib/indexer.ts) - Legal corpus seeder: 7 core Spanish laws (Codigo Civil, Constitucion, LEC, ET, LOPDGDD, Codigo Penal, LGT) - Semantic search API (POST /v1/rag/search) with jurisdiction and source type filters - Admin-only indexing endpoint (POST /v1/rag/index) - RAG context automatically injected into chat (conversations route retrieves relevant legal sources) - Graceful degradation: chat works without RAG if embeddings unavailable
  • Contact Page, Deadlines, and Citation Verifier - Contact page with form (name, email, company, subject selector, message) + info cards (email, hours, location) - Contact dictionary entries added to both ES/EN - Deadline management system: full CRUD API, stats, upcoming deadlines, auto-status - Deadlines UI: create form, filtered list, completion toggle, priority badges, days-until-due indicators - Deadlines DB schema pushed to Neon (deadlines table + enums + indexes) - Anti-hallucination citation verifier: regex extraction + AI verification + confidence scoring - Verification API endpoint (POST /v1/verify) for on-demand citation checking - Known-law database covering 25+ major Spanish statutes - Sidebar navigation: deadlines page enabled (removed "Pronto" badge) - API client: added PATCH method for update operations
  • +12 more
Corrección
  • Docker build: missing public directories (.gitkeep), deprecated `--loader tsx` flag
  • Drizzle config: .env resolution from monorepo root
  • Git default branch: master -> main
v0.1.0
2026-02-28
Añadido
  • Initial monorepo structure (Turborepo + npm workspaces)
  • Database schema with Drizzle ORM: organizations, users, conversations, messages, documents, legal_sources, billing
  • API skeleton with Hono.js: health, auth routes, JWT middleware, rate limiting, CORS
  • Shared package with i18n dictionaries (ES+EN), Zod schemas, constants
  • Marketing site scaffold (Next.js 15) with i18n routing
  • Lawyer workspace scaffold (Next.js 15) with sidebar navigation and dashboard layout
  • Dev environment: Caddyfile, .claude/servers.json, port allocation (4965-4967)
  • Docker configuration: Dockerfile.web, Dockerfile.app, Dockerfile.api
  • +1 more

For the full history, see the CHANGELOG.md in the repository.