diff --git a/AGENTS.md b/AGENTS.md index 7b9531a..0267164 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,7 +13,7 @@ - Tracking solo con consentimiento previo y granular (CMP `vanilla-cookieconsent` v3; Consent Mode v2 con todo `denied` por defecto). Vercel Analytics y Speed Insights solo en builds Vercel (`__IS_VERCEL__`) y solo tras consentimiento de analítica; en local/CI no existen `/_vercel/*` y romperían Best Practices. Medición comercial: `form_submit` = intento; Meta Lead y `contact_form_success` solo tras `200` con `status: 'accepted'` y sin replay. - Tracking comercial completo **autorizado** (giro mercenario): GA4, Meta Pixel + CAPI server-side, LinkedIn Insight, Microsoft Clarity y PostHog EU, además de GTM server-side en Vercel. Todos gated por consentimiento de su categoría; ninguna etiqueta no esencial se carga sin consentimiento. - Las cookies deben figurar en `docs/cookies-inventory.json`; `pnpm run audit:cookies` es la verificación canónica. La categoría de marketing del CMP se muestra como **Publicidad** (clave técnica `marketing`). -- Catálogo autorizado: informe comercial, sesión profesional, auditoría, y portal de inicio con módulos de coste añadido. Importes **provisionales** en `src/content/pricing.ts` hasta fijación explícita; no añadir CMS ni Google Fonts ni ampliar servicios sin decisión. +- Catálogo autorizado (decisión 0020): posicionamiento en buscadores (`informe-comercial`), cumplimiento normativo, ciberseguridad, colaboración en software (`software-a-medida`), y portal de inicio solo en el configurador. Importes **provisionales** en `src/content/pricing.ts` hasta fijación explícita; no añadir CMS ni Google Fonts ni ampliar servicios sin decisión. - Preferir repo público + Vercel Hobby para preview por PR (decisión 1B); relajar Deployment Protection/SSO de previews para URLs compartibles (decisión 2A). - Versionado (semantic-release / release.yml) distinto de promote a producción; content=patch, feat=minor, breaking=major. - Al trabajar desde `main`, abrir rama `cursor/…`, commit/push y PR en borrador (no push directo a default). @@ -31,4 +31,4 @@ - Release: semantic-release en `main` crea **tag + GitHub Release**; no usa `@semantic-release/git` (ruleset PR-only). Fuente de verdad de versión = tag. `main-watch.yml` abre issues `alerta:main` si CI/security/Release fallan en `main` o si falla deploy Production; respuesta técnica inmediata documentada en `docs/standards/protocols/ci-release.md`. - Renovate (`.github/renovate.json`) es el único bot de version-updates (`npm` + `github-actions`). Stack majors al día: Astro 7, Zod 4, nodemailer 10, React 19 (issue #12 cerrado). - Security: `security.yml` (gitleaks, osv-scanner + `osv-scanner.toml`, CodeQL sin upload); scorecard y dependency-review solo si el repo es público. Residuales documentados en `docs/threat-model.md`. -- IA (decisión 0019; supersede 0014–0018): nav Servicios, Proyectos realizados (`/proyectos`), Cómo trabajo (`/proceso-y-entrega`); aliases 308 desde metodología, valores y como-trabajo. `/sobre-mi` es página viva. Home = hero estable + tres caminos + tira Cómo trabajo + un caso. Proyectos declaran `availability` y `productType` (Web · Tienda · Herramienta). Fichas: informe-comercial, sesion-profesional, auditoría y software-a-medida (sin precio «desde» inventado); portal solo vía configurador. +- IA (decisión 0020; supersede 0019 en catálogo): nav Servicios, Proyectos realizados (`/proyectos`), Cómo trabajo (`/proceso-y-entrega`); aliases 308 desde metodología, valores y como-trabajo. `/sobre-mi` es página viva. Home = hero estable + tres caminos + tira Cómo trabajo + un caso. Proyectos declaran `availability` y `productType` (Web · Tienda · Herramienta). Fichas: informe-comercial, cumplimiento-normativo, ciberseguridad y software-a-medida (sin precio «desde» inventado); portal solo vía configurador. Sesión profesional retirada. diff --git a/README.md b/README.md index f23cf30..fa2b0f2 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,20 @@ Sitio profesional de [alexendros.dev](https://alexendros.dev): webs que explican ## En qué puedo ayudarte -| Servicio | Para quién | -| --------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | -| [Portal de inicio (web/landing)](https://alexendros.dev/servicios/#portal-heading) | Profesionales y negocios locales que necesitan una web clara lista para captar consultas | -| [Informe 360º](https://alexendros.dev/servicios/informe-comercial/) | Quienes quieren saber qué mejorar primero, con prioridades y presupuesto realista | -| [Sesión profesional](https://alexendros.dev/servicios/sesion-profesional/) | Decisiones concretas en 60 minutos, con siguientes pasos por escrito | -| [Inspección normativa y vulnerabilidades](https://alexendros.dev/servicios/auditoria-normativa-ciberseguridad/) | Informe accionable de RGPD/LSSI y seguridad básica | -| [Software a medida](https://alexendros.dev/servicios/software-a-medida/) | Herramientas por fases para un proceso de negocio | +| Servicio | Para quién | +| -------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- | +| [Portal de inicio (web/landing)](https://alexendros.dev/servicios/#portal-heading) | Profesionales y negocios locales que necesitan una web clara lista para captar consultas | +| [Posicionamiento en buscadores](https://alexendros.dev/servicios/informe-comercial/) | Quienes necesitan un informe SEO técnico y un plan de ajustes con métricas verificables | +| [Cumplimiento normativo](https://alexendros.dev/servicios/cumplimiento-normativo/) | Protección de datos, políticas de cookies y accesibilidad | +| [Ciberseguridad](https://alexendros.dev/servicios/ciberseguridad/) | Vulnerabilidades básicas priorizadas, sin prueba de intrusión | +| [Colaboración en proyectos de software](https://alexendros.dev/servicios/software-a-medida/) | Refuerzo técnico por fases en proyectos grandes con más compañeros | -Más detalle y precios orientativos (+ IVA) en [Servicios](https://alexendros.dev/servicios/). Cómo trabajo en [Proceso y entrega](https://alexendros.dev/proceso-y-entrega/). Proyectos reales en [Proyectos](https://alexendros.dev/proyectos/). +Más detalle y precios orientativos (+ IVA) en [Hoja de Servicios](https://alexendros.dev/servicios/). Cómo trabajo en [Proceso y entrega](https://alexendros.dev/proceso-y-entrega/). Proyectos reales en [Proyectos](https://alexendros.dev/proyectos/). ## Cómo empezamos 1. Cuéntame tu situación en [Contacto](https://alexendros.dev/contacto/) o escribe a [contratacion@alexendros.dev](mailto:contratacion@alexendros.dev). -2. Si prefieres una cita: [Cal.com](https://cal.com/alexendros) o [sesión técnica](https://cal.com/alexendros/sesion-tecnica). +2. Si prefieres una llamada breve sin coste: [diagnóstico en Cal.com](https://cal.com/alexendros/diagnostico). 3. Acordamos alcance, plazos y entregables antes de empezar. Trabajo desde Valencia (remoto o presencial según el proyecto). El sitio prioriza claridad, accesibilidad y velocidad; el tracking comercial solo se carga con consentimiento previo y granular. diff --git a/docs/audits/decisions.json b/docs/audits/decisions.json index 8cc68c2..5f44664 100644 --- a/docs/audits/decisions.json +++ b/docs/audits/decisions.json @@ -276,16 +276,31 @@ "id": "0019", "sequence": 19, "timestamp": "2026-10-18T16:30:00Z", - "active": true, + "active": false, "title": "Proceso comercial, software a medida y páginas de confianza", "summary": "Reescribe /proceso-y-entrega con plazos y precios ya publicados; añade ficha software-a-medida sin cifra inventada; home con tres caminos; restaura /sobre-mi.", "recordType": "decision", - "status": "aceptada", + "status": "superseded", "supersedes": ["0014", "0016", "0017", "0018"], - "supersededBy": null, + "supersededBy": "0020", "context": "El plan comercial exige densidad baja, plazos y pago visibles en Cómo trabajo, una vía de software a medida y coherencia home/servicios/proyectos. 0014/0016 fijaban home solo-hero; 0017 limitaba a tres fichas; 0018 prohibía euros en proceso y redirigía /sobre-mi.", "decision": "Acotar 0018: /proceso-y-entrega puede mostrar euros y plazos solo donde pricing.ts y el catálogo ya los tienen; se mantienen nav Cómo trabajo y 308 de /como-trabajo, /metodologia y /valores. Restaurar /sobre-mi como página (profile.ts) y retirar su 308. Acotar 0014/0016: home conserva H1 estable y CTAs del hero; añade tres caminos, tira Cómo trabajo y un caso. Acotar 0017: cuarta ficha /servicios/software-a-medida sin precio «desde» inventado (precio por fase en la propuesta); portal sigue solo en el configurador. Precios visibles como + IVA y una sola nota de provisionalidad. origen allowlist en contacto junto a motivo.", - "consequences": "Tests de proceso, products-structure (cuatro slugs), home-grid-nav, redirects y sitemap se actualizan. OG propia de proceso y de la ficha nueva. Artículos estáticos y README alineados al catálogo." + "consequences": "Superseded por 0020 (catálogo). Nav Cómo trabajo, home con caminos, /sobre-mi y software por fases siguen vigentes vía 0020." + }, + { + "id": "0020", + "sequence": 20, + "timestamp": "2026-10-18T18:20:00Z", + "active": true, + "title": "Hoja de Servicios: catálogo SEO, cumplimiento, ciberseguridad y colaboración", + "summary": "Retira sesión profesional; parte la inspección en cumplimiento y ciberseguridad; renombra informe y software; índice como Hoja de Servicios.", + "recordType": "decision", + "status": "aceptada", + "supersedes": ["0019"], + "supersededBy": null, + "context": "El catálogo de 0019 mezclaba normativa y vulnerabilidades, ofrecía sesión de pago como ficha y titulaba el informe como 360º sin foco SEO comercial.", + "decision": "Publicar cuatro fichas: informe-comercial (Posicionamiento comercial en buscadores), cumplimiento-normativo, ciberseguridad y software-a-medida (Colaboración en proyectos de software). Retirar sesion-profesional con 308 a /servicios; redirigir auditoria-normativa-ciberseguridad a cumplimiento-normativo. Índice H1 «Hoja de Servicios». Portal solo en configurador. Conservar de 0019: nav Cómo trabajo, home con tres caminos, /sobre-mi, euros/plazos en proceso donde ya existan, y software sin precio «desde» inventado.", + "consequences": "Allowlist de contacto, products-structure, OG por slug, README y redirects actualizados. Contacto ya no ofrece reserva Cal de sesión profesional." } ] } diff --git a/public/og/servicios.png b/public/og/servicios.png index 201af06..725c22b 100644 Binary files a/public/og/servicios.png and b/public/og/servicios.png differ diff --git a/public/og/servicios/auditoria-normativa-ciberseguridad.png b/public/og/servicios/auditoria-normativa-ciberseguridad.png deleted file mode 100644 index 21d7e30..0000000 Binary files a/public/og/servicios/auditoria-normativa-ciberseguridad.png and /dev/null differ diff --git a/public/og/servicios/ciberseguridad.png b/public/og/servicios/ciberseguridad.png new file mode 100644 index 0000000..8c186ad Binary files /dev/null and b/public/og/servicios/ciberseguridad.png differ diff --git a/public/og/servicios/cumplimiento-normativo.png b/public/og/servicios/cumplimiento-normativo.png new file mode 100644 index 0000000..a8325ba Binary files /dev/null and b/public/og/servicios/cumplimiento-normativo.png differ diff --git a/public/og/servicios/informe-comercial.png b/public/og/servicios/informe-comercial.png index dec3dfd..9b11b72 100644 Binary files a/public/og/servicios/informe-comercial.png and b/public/og/servicios/informe-comercial.png differ diff --git a/public/og/servicios/sesion-profesional.png b/public/og/servicios/sesion-profesional.png deleted file mode 100644 index cbc6767..0000000 Binary files a/public/og/servicios/sesion-profesional.png and /dev/null differ diff --git a/public/og/servicios/software-a-medida.png b/public/og/servicios/software-a-medida.png index 6da7217..265de34 100644 Binary files a/public/og/servicios/software-a-medida.png and b/public/og/servicios/software-a-medida.png differ diff --git a/scripts/gen-og.mts b/scripts/gen-og.mts index f009191..2e4a257 100644 --- a/scripts/gen-og.mts +++ b/scripts/gen-og.mts @@ -94,7 +94,7 @@ await writeOg( 'Para profesionales y pequeños negocios' ); await writeOg('home.png', 'ALEXENDROS.DEV', 'Webs y software a medida', 'Y revisión de la tuya'); -await writeOg('servicios.png', 'SERVICIOS', 'Qué puedo hacer por ti', 'Web, software y revisión'); +await writeOg('servicios.png', 'SERVICIOS', 'Hoja de Servicios', 'Web, software y revisión'); await writeOg( 'proyectos.png', 'CASOS', diff --git a/src/components/PricingConfigurator.tsx b/src/components/PricingConfigurator.tsx index c1a332d..75fbe30 100644 --- a/src/components/PricingConfigurator.tsx +++ b/src/components/PricingConfigurator.tsx @@ -36,7 +36,7 @@ export default function PricingConfigurator() { return (

- Configura los módulos que querrás en tu portal web + Si quieres que te diseñe un portal web desde cero, aquí puedes configurar los módulos.

Base del portal: {portalBase.delivery}. Pago: {portalBase.payment}. Plazo:{' '} diff --git a/src/components/StickyCta.astro b/src/components/StickyCta.astro index f4c478d..e6e7338 100644 --- a/src/components/StickyCta.astro +++ b/src/components/StickyCta.astro @@ -24,11 +24,11 @@ const priceLabel = `${formatEuro(provisionalPrices.informeComercial)} + IVA`; id="sticky-cta" class="fixed bottom-0 left-0 right-0 z-30 hidden border-t border-border glass-card backdrop-blur md:hidden pb-safe" role="region" - aria-label="Informe 360º" + aria-label="Posicionamiento en buscadores" >

- Informe 360º {priceLabel} + Posicionamiento SEO {priceLabel} · orientativo

diff --git a/src/content/articles.ts b/src/content/articles.ts index 4b3e442..bd266b3 100644 --- a/src/content/articles.ts +++ b/src/content/articles.ts @@ -32,7 +32,7 @@ export const articles: Article[] = [ 'Si el problema es un proceso interno (listados, pedidos, cálculos, agendas), una web de escaparate no lo resuelve. Ahí entra el software a medida.', 'El software se plantea por fases: primero una versión pequeña y útil, luego mejoras. Cada fase lleva su precio y plazo en la propuesta.', 'Una señal clara: si hoy el trabajo vive en hojas de cálculo, correos o herramientas que no encajan, conviene descubrir el proceso antes de construir.', - 'Si dudas, empieza por una revisión (Informe 360º) o una conversación breve. Mejor acotar el problema que pagar una pieza grande sin saber si sirve.' + 'Si dudas, empieza por una revisión de posicionamiento o una conversación breve. Mejor acotar el problema que pagar una pieza grande sin saber si sirve.' ] } ]; diff --git a/src/content/commercial-coherence.test.ts b/src/content/commercial-coherence.test.ts index b716428..17f2fe2 100644 --- a/src/content/commercial-coherence.test.ts +++ b/src/content/commercial-coherence.test.ts @@ -26,15 +26,16 @@ describe('COM-01 coherencia comercial vigente', () => { } }); - it('contacto y catálogo alinean importes de informe y sesión; Cal.com se conserva', () => { - const informe = contact.bookings.offers.find((o) => o.id === 'informe-comercial'); - const sesion = contact.bookings.offers.find((o) => o.id === 'sesion-profesional'); - expect(informe?.priceLabel).toBe(formatEuro(provisionalPrices.informeComercial)); - expect(sesion?.priceLabel).toBe(formatEuro(provisionalPrices.sesionProfesional)); - expect(sesion?.kind).toBe('cal'); - if (sesion?.kind === 'cal') { - expect(sesion.url).toMatch(/^https:\/\/cal\.com\//); - } + it('contacto y catálogo alinean importes de las ofertas de formulario', () => { + const byId = Object.fromEntries(contact.bookings.offers.map((o) => [o.id, o])); + expect(byId['informe-comercial']?.priceLabel).toBe( + formatEuro(provisionalPrices.informeComercial) + ); + expect(byId['cumplimiento-normativo']?.priceLabel).toBe( + formatEuro(provisionalPrices.cumplimiento) + ); + expect(byId['ciberseguridad']?.priceLabel).toBe(formatEuro(provisionalPrices.ciberseguridad)); + expect(contact.bookings.offers.every((o) => o.kind === 'form')).toBe(true); }); it('el configurador usa la base provisional del portal sin inventar cancelación', () => { diff --git a/src/content/contact.test.ts b/src/content/contact.test.ts index 456f5ac..a413ce4 100644 --- a/src/content/contact.test.ts +++ b/src/content/contact.test.ts @@ -2,22 +2,20 @@ import { describe, expect, it } from 'vitest'; import { contact, publicBookingEvents } from './contact'; describe('reservas públicas de contacto R05', () => { - it('el informe no tiene Cal.com y la sesión apunta a sesion-tecnica', () => { - const informe = contact.bookings.offers.find((o) => o.id === 'informe-comercial'); - const sesion = contact.bookings.offers.find((o) => o.id === 'sesion-profesional'); - expect(informe?.kind).toBe('form'); - if (informe?.kind === 'form') { - expect(informe.href).toBe('#escribir'); - expect(JSON.stringify(informe)).not.toMatch(/cal\.com/i); + it('las ofertas de catálogo son formulario; no hay Cal.com de sesión profesional', () => { + expect(contact.bookings.offers.every((o) => o.kind === 'form')).toBe(true); + expect(contact.bookings.offers.map((o) => o.id)).toEqual([ + 'informe-comercial', + 'cumplimiento-normativo', + 'ciberseguridad' + ]); + for (const offer of contact.bookings.offers) { + expect(offer.href).toBe('#escribir'); + expect(JSON.stringify(offer)).not.toMatch(/cal\.com/i); } - expect(sesion?.kind).toBe('cal'); - if (sesion?.kind === 'cal') { - expect(sesion.calLink).toBe('alexendros/sesion-tecnica'); - expect(sesion.url).toBe('https://cal.com/alexendros/sesion-tecnica'); - } - expect(publicBookingEvents.map((e) => e.calLink)).toEqual(['alexendros/sesion-tecnica']); + expect(publicBookingEvents).toEqual([]); + expect(JSON.stringify(contact)).not.toMatch(/sesion-profesional/i); expect(JSON.stringify(contact)).not.toMatch(/retainer/i); - expect(JSON.stringify(contact)).not.toMatch(/diagnostico/i); }); it('expone formulario y perfiles sociales', () => { diff --git a/src/content/contact.ts b/src/content/contact.ts index 62d7e17..b2c0bf0 100644 --- a/src/content/contact.ts +++ b/src/content/contact.ts @@ -1,28 +1,15 @@ import { z } from 'zod'; import { formatEuro, provisionalPrices } from './pricing'; -import { calEventUrl } from '../lib/calBooking'; -export const bookingOfferSchema = z.discriminatedUnion('kind', [ - z.object({ - kind: z.literal('form'), - id: z.literal('informe-comercial'), - title: z.string().min(1), - priceLabel: z.string().min(1), - summary: z.string().min(1), - cta: z.string().min(1), - href: z.string().min(1) - }), - z.object({ - kind: z.literal('cal'), - id: z.literal('sesion-profesional'), - calLink: z.string().regex(/^alexendros\/[a-z0-9-]+$/), - url: z.string().url(), - title: z.string().min(1), - priceLabel: z.string().min(1), - summary: z.string().min(1), - cta: z.string().min(1) - }) -]); +export const bookingOfferSchema = z.object({ + kind: z.literal('form'), + id: z.enum(['informe-comercial', 'cumplimiento-normativo', 'ciberseguridad']), + title: z.string().min(1), + priceLabel: z.string().min(1), + summary: z.string().min(1), + cta: z.string().min(1), + href: z.string().min(1) +}); export const contactChannelSchema = z.object({ id: z.string().min(1), @@ -41,7 +28,7 @@ export const contactSchema = z.object({ bookings: z.object({ heading: z.string().min(1), intro: z.string().min(1), - offers: z.array(bookingOfferSchema).length(2) + offers: z.array(bookingOfferSchema).min(1).max(3) }) }); @@ -76,33 +63,50 @@ export const contact = contactSchema.parse({ }, bookings: { heading: 'Opciones de contacto', - intro: 'Informe 360º por formulario. Sesión profesional con reserva y pago en Cal.com.', + intro: + 'Posicionamiento en buscadores, cumplimiento normativo y ciberseguridad se piden por formulario.', offers: [ { kind: 'form', id: 'informe-comercial', - title: 'Informe 360º', + title: 'Posicionamiento comercial en buscadores', priceLabel: formatEuro(provisionalPrices.informeComercial), summary: - 'Informe completo de tu website actual: posicionamiento, velocidad, accesibilidad y plan de mejoras.', + 'Informe técnico SEO y plan de ajustes con métricas verificables para mejorar el posicionamiento.', cta: 'Rellenar formulario', href: '#escribir' }, { - kind: 'cal', - id: 'sesion-profesional', - calLink: 'alexendros/sesion-tecnica', - url: calEventUrl('alexendros/sesion-tecnica'), - title: 'Sesión profesional', - priceLabel: formatEuro(provisionalPrices.sesionProfesional), + kind: 'form', + id: 'cumplimiento-normativo', + title: 'Cumplimiento normativo', + priceLabel: formatEuro(provisionalPrices.cumplimiento), + summary: 'Revisión de protección de datos, políticas de cookies y accesibilidad en tu web.', + cta: 'Rellenar formulario', + href: '#escribir' + }, + { + kind: 'form', + id: 'ciberseguridad', + title: 'Ciberseguridad', + priceLabel: formatEuro(provisionalPrices.ciberseguridad), summary: - 'Sesión de mejoras sobre tu página actual. 50 % de descuento si aplicas el plan de una inspección.', - cta: 'Reservar sesión profesional' + 'Revisión de vulnerabilidades básicas con severidad y corrección recomendada por problema.', + cta: 'Rellenar formulario', + href: '#escribir' } ] } }); -export const publicBookingEvents = contact.bookings.offers.filter( - (o): o is Extract<(typeof contact.bookings.offers)[number], { kind: 'cal' }> => o.kind === 'cal' -); +/** Ya no hay ofertas Cal.com en la página de contacto; se conserva el tipo vacío. */ +export const publicBookingEvents: Array<{ + kind: 'cal'; + calLink: string; + url: string; + title: string; + priceLabel: string; + summary: string; + cta: string; + id: string; +}> = []; diff --git a/src/content/pricing.ts b/src/content/pricing.ts index 336e5ad..3589e6d 100644 --- a/src/content/pricing.ts +++ b/src/content/pricing.ts @@ -1,8 +1,8 @@ /** Importes provisionales. Un solo sitio para sustituirlos cuando se fijen. */ export const provisionalPrices = { informeComercial: 120, - sesionProfesional: 300, - auditoria: 200, + cumplimiento: 200, + ciberseguridad: 200, portalInicio: 495, tienda: 700, mensajeria: 200, diff --git a/src/content/process.ts b/src/content/process.ts index 7dfca0b..3e22c79 100644 --- a/src/content/process.ts +++ b/src/content/process.ts @@ -121,8 +121,8 @@ export const processPage = { }, { id: 'software', - title: 'Software a medida', - body: 'Una herramienta por fases, empezando por una versión pequeña.', + title: 'Colaboración en software', + body: 'Me integro en proyectos grandes con el equipo, por fases.', priceLabel: 'Precio por fase en la propuesta', href: `/servicios/software-a-medida?${ORIGIN}`, linkLabel: 'Ver cómo se plantea' @@ -130,10 +130,10 @@ export const processPage = { { id: 'revision', title: 'Revisar tu web', - body: 'Te digo qué falla y por dónde empezar.', + body: 'Informe SEO con métricas y plan de ajustes.', priceLabel: `Desde ${formatEuro(provisionalPrices.informeComercial)} + IVA`, href: `/servicios/informe-comercial?${ORIGIN}`, - linkLabel: 'Ver Informe 360º' + linkLabel: 'Ver posicionamiento en buscadores' } ] satisfies ProcessPathCard[], featuredCase: { diff --git a/src/content/redirects.test.ts b/src/content/redirects.test.ts index eefe4e3..547aecd 100644 --- a/src/content/redirects.test.ts +++ b/src/content/redirects.test.ts @@ -24,7 +24,11 @@ describe('redirecciones permanentes', () => { ['/servicios/produccion-sitios-web', '/servicios'], ['/servicios/auditorias', '/servicios/informe-comercial'], ['/servicios/consultoria-tecnologica', '/servicios'], - ['/servicios/landing-10-dias', '/servicios'] + ['/servicios/landing-10-dias', '/servicios'], + ['/servicios/sesion-profesional', '/servicios'], + ['/servicios/sesion-profesional/', '/servicios'], + ['/servicios/auditoria-normativa-ciberseguridad', '/servicios/cumplimiento-normativo'], + ['/servicios/auditoria-normativa-ciberseguridad/', '/servicios/cumplimiento-normativo'] ]; for (const [source, destination] of expected) { diff --git a/src/content/services.catalog.test.ts b/src/content/services.catalog.test.ts index f81c9e6..ca24f2a 100644 --- a/src/content/services.catalog.test.ts +++ b/src/content/services.catalog.test.ts @@ -28,42 +28,42 @@ const commercialFixture = { payment: 'Pago único antes de empezar', timeline: '5 días laborables', delivery: - 'Dossier con problemas, límites del análisis y plan priorizado de mejoras (sin implementación)', + 'Informe técnico SEO, métricas de partida y plan priorizado de ajustes (sin implementación)', guarantee: 'Una ronda de aclaraciones en 10 días naturales', scope: [ - 'SEO técnico básico y señales de posicionamiento', - 'Velocidad de carga y uso en móvil', - 'Accesibilidad al navegarlo', - 'Claridad de mensaje y camino a contacto', - 'Priorización de problemas con límites explícitos' + 'SEO técnico: indexación, estructura, metadatos y señales on-page', + 'Rendimiento y uso en móvil ligados al posicionamiento', + 'Claridad del camino a contacto desde resultados de búsqueda', + 'Plan priorizado de ajustes con métricas de referencia', + 'Límites explícitos del análisis (sin garantía de ranking)' ] }, - 'sesion-profesional': { + 'cumplimiento-normativo': { mode: 'fixed' as const, - priceEuros: provisionalPrices.sesionProfesional, - payment: - 'Pago al reservar en Cal.com (importe orientativo). 50 % de descuento si aplicas el plan de una inspección (360º o vulnerabilidades)', - timeline: '60 minutos', - delivery: 'Siguientes pasos por escrito en 1 día laborable', - guarantee: 'Resumen entregado en 1 día laborable tras la sesión', + priceEuros: provisionalPrices.cumplimiento, + payment: '50 % al encargo y 50 % a la entrega, sin recargo', + timeline: '10 días laborables', + delivery: + 'Informe de protección de datos, cookies y accesibilidad; cada problema con severidad y corrección recomendada', + guarantee: 'Corrección de desviaciones del alcance escrito en 15 días naturales', scope: [ - 'Conversación de 60 minutos', - 'Revisión orientada de un problema concreto', - 'Resumen de siguientes pasos por escrito' + 'Protección de datos en la superficie pública (RGPD y LSSI aplicables)', + 'Políticas de cookies y consentimiento', + 'Accesibilidad al navegarlo (revisión práctica, no certificación)', + 'Priorización por severidad' ] }, - 'auditoria-normativa-ciberseguridad': { + ciberseguridad: { mode: 'fixed' as const, - priceEuros: provisionalPrices.auditoria, + priceEuros: provisionalPrices.ciberseguridad, payment: '50 % al encargo y 50 % a la entrega, sin recargo', timeline: '10 días laborables', - delivery: - 'Informe RGPD/LSSI y seguridad básica; cada problema con severidad y corrección recomendada', + delivery: 'Informe de seguridad básica; cada problema con severidad y corrección recomendada', guarantee: 'Corrección de desviaciones del alcance escrito en 15 días naturales', scope: [ - 'RGPD y LSSI en la superficie pública', 'Seguridad básica (cabeceras, secretos expuestos, superficies evidentes)', - 'Priorización por severidad' + 'Priorización por severidad', + 'Corrección recomendada por problema' ] }, 'software-a-medida': { @@ -74,10 +74,10 @@ const commercialFixture = { delivery: 'Código, documentación y accesos de la fase acordada', guarantee: 'Si algo no cumple lo acordado en esa fase, lo corrijo sin coste.', scope: [ - 'Descubrimiento del problema y del alcance de la primera versión', - 'Diseño y construcción de la primera versión usable', + 'Alineación con el equipo y el alcance de la fase', + 'Diseño y construcción de entregables acordados', 'Mejoras por fases con precio y plazo escritos antes de cada una', - 'Entrega de código, documentación y accesos al terminar de pagar' + 'Entrega de código, documentación y accesos al terminar de pagar la fase' ] } } as const; @@ -146,7 +146,7 @@ describe('CAT catálogo de servicios', () => { serviceActionSchema.safeParse({ kind: 'booking', label: 'Reservar', - href: 'https://cal.com/alexendros/sesion-tecnica', + href: 'https://cal.com/alexendros/diagnostico', newTab: true }).success ).toBe(true); diff --git a/src/content/services.ts b/src/content/services.ts index 4028324..3f63840 100644 --- a/src/content/services.ts +++ b/src/content/services.ts @@ -151,11 +151,11 @@ export const portalModuleSchema = z.object({ export type PortalModule = z.infer; -/** Slugs publicados como ficha `/servicios/[slug]` (decisión 0019; portal sin ficha). */ +/** Slugs publicados como ficha `/servicios/[slug]` (decisión 0020; portal sin ficha). */ export const PUBLISHED_SERVICE_SLUGS = [ 'informe-comercial', - 'sesion-profesional', - 'auditoria-normativa-ciberseguridad', + 'cumplimiento-normativo', + 'ciberseguridad', 'software-a-medida' ] as const; @@ -163,38 +163,42 @@ export const services: Service[] = [ { slug: 'informe-comercial', kind: 'service', - title: 'Informe 360º', + title: 'Posicionamiento comercial en buscadores', short: - 'Reviso tu web y te entrego los problemas detectados, sus límites y un plan priorizado de mejoras.', + 'Informe técnico SEO y plan de ajustes con métricas verificables para ganar visibilidad en buscadores.', description: - 'Reviso tu website actual: SEO técnico básico, velocidad de carga, accesibilidad al navegarlo y claridad del camino a contacto. Te entrego un dossier con problemas, límites del análisis y un plan priorizado de mejoras. No promete posiciones en buscadores ni resolver cada problema sin encargo aparte.', - audience: ['Negocios y profesionales que necesitan saber por dónde empezar antes de invertir.'], + 'Reviso tu web con foco en SEO técnico y señales de posicionamiento. Te entrego un informe con problemas medibles, un plan de ajustes priorizado y métricas de referencia para comprobar la mejora. No promete posiciones concretas ni implementa los cambios sin encargo aparte.', + audience: [ + 'Negocios y profesionales que quieren mejorar su visibilidad en buscadores con un plan accionable.' + ], problems: [ - 'La web no trae contactos', - 'No sabes qué corregir primero', - 'Has recibido propuestas difíciles de priorizar' + 'La web no aparece o pierde posiciones en buscadores', + 'No sabes qué corregir primero en SEO técnico', + 'Necesitas métricas claras antes de invertir en mejoras' ], scope: [ - 'SEO técnico básico y señales de posicionamiento', - 'Velocidad de carga y uso en móvil', - 'Accesibilidad al navegarlo', - 'Claridad de mensaje y camino a contacto', - 'Priorización de problemas con límites explícitos' + 'SEO técnico: indexación, estructura, metadatos y señales on-page', + 'Rendimiento y uso en móvil ligados al posicionamiento', + 'Claridad del camino a contacto desde resultados de búsqueda', + 'Plan priorizado de ajustes con métricas de referencia', + 'Límites explícitos del análisis (sin garantía de ranking)' ], deliverables: [ - 'Dossier con problemas, necesidades y límites del análisis', - 'Plan priorizado de mejoras (sin implementación incluida)' + 'Informe técnico con problemas, métricas de partida y límites del análisis', + 'Plan de ajustes y mejoras priorizado (sin implementación incluida)' ], process: [ 'Encargo y acceso al dominio', - 'Inspección 360º', - 'Redacción del dossier y del plan', + 'Análisis SEO técnico y de señales comerciales', + 'Redacción del informe y del plan', 'Entrega y ronda de aclaraciones' ], - technicalDetails: ['Análisis CWV, SEO técnico básico, revisión de contenidos y accesibilidad.'], + technicalDetails: [ + 'Revisión de indexación, CWV orientados a SEO, metadatos, enlazado interno y contenido indexable.' + ], exclusions: [ 'No incluye implementación de las mejoras', - 'No incluye prueba de intrusión ni certificación oficial', + 'No incluye campañas de publicidad ni link building', 'No garantiza posiciones en buscadores' ], commercial: { @@ -204,7 +208,7 @@ export const services: Service[] = [ payment: 'Pago único antes de empezar', timeline: '5 días laborables', delivery: - 'Dossier con problemas, límites del análisis y plan priorizado de mejoras (sin implementación)', + 'Informe técnico SEO, métricas de partida y plan priorizado de ajustes (sin implementación)', guarantee: 'Una ronda de aclaraciones en 10 días naturales' }, action: { @@ -215,133 +219,145 @@ export const services: Service[] = [ } }, { - slug: 'sesion-profesional', + slug: 'cumplimiento-normativo', kind: 'service', - title: 'Sesión profesional', + title: 'Cumplimiento normativo', short: - 'Una sesión de 60 minutos para revisar un problema concreto de tu web y decidir los siguientes pasos.', + 'Revisión de protección de datos, políticas de cookies y accesibilidad en tu presencia web.', description: - 'Sesión de 60 minutos orientada a un problema concreto de tu página actual. Orienta y entrega un resumen escrito; no incluye implementación salvo acuerdo aparte. 50 % de descuento si la sesión aplica el plan de una de mis inspecciones (Informe 360º o inspección de vulnerabilidades).', - audience: ['Quien necesita desbloquear una decisión técnica o de producto con plazo corto.'], + 'Inspección orientada a protección de datos, políticas de cookies y accesibilidad de tu sitio. Cada problema lleva severidad y corrección recomendada. No incluye prueba de intrusión ni certificación oficial.', + audience: [ + 'Negocios que necesitan un informe accionable de datos, cookies y accesibilidad antes de endurecer su sitio.' + ], problems: [ - 'Hay un bloqueo concreto', - 'Necesitas decidir el siguiente paso', - 'No quieres un proyecto completo todavía' + 'Dudas de protección de datos (RGPD) en la web', + 'Políticas de cookies poco claras o incompletas', + 'Accesibilidad insuficiente al navegar o al cumplir requisitos básicos' ], scope: [ - 'Conversación de 60 minutos', - 'Revisión orientada de un problema concreto', - 'Resumen de siguientes pasos por escrito' + 'Protección de datos en la superficie pública (RGPD y LSSI aplicables)', + 'Políticas de cookies y consentimiento', + 'Accesibilidad al navegarlo (revisión práctica, no certificación)', + 'Priorización por severidad' + ], + deliverables: [ + 'Informe de protección de datos, cookies y accesibilidad', + 'Severidad y corrección recomendada por problema' + ], + process: ['Alcance del encargo', 'Análisis', 'Redacción del informe', 'Entrega'], + technicalDetails: [ + 'Revisión de avisos legales, cookies, formularios y barreras evidentes de accesibilidad.' ], - deliverables: ['Resumen de siguientes pasos por escrito (sin implementación incluida)'], - process: ['Reserva en Cal.com', 'Sesión de 60 minutos', 'Entrega del resumen'], - technicalDetails: ['Cal.com para reserva y pago del importe orientativo publicado.'], exclusions: [ - 'No incluye implementación', - 'No incluye auditoría completa', - 'La implementación solo entra con acuerdo aparte' + 'No incluye certificación oficial ni auditoría legal completa', + 'No incluye prueba de intrusión ni análisis de vulnerabilidades técnicas', + 'No incluye implementación de remedios' ], commercial: { mode: 'fixed', - priceEuros: provisionalPrices.sesionProfesional, + priceEuros: provisionalPrices.cumplimiento, priceStatus: 'provisional', - payment: - 'Pago al reservar en Cal.com (importe orientativo). 50 % de descuento si aplicas el plan de una inspección (360º o vulnerabilidades)', - timeline: '60 minutos', - delivery: 'Siguientes pasos por escrito en 1 día laborable', - guarantee: 'Resumen entregado en 1 día laborable tras la sesión' + payment: '50 % al encargo y 50 % a la entrega, sin recargo', + timeline: '10 días laborables', + delivery: + 'Informe de protección de datos, cookies y accesibilidad; cada problema con severidad y corrección recomendada', + guarantee: 'Corrección de desviaciones del alcance escrito en 15 días naturales' }, action: { - kind: 'booking', - label: 'Reservar sesión', - href: 'https://cal.com/alexendros/sesion-tecnica', - newTab: true + kind: 'contact', + label: 'Pedir la revisión', + href: '/contacto?motivo=informacionCliente&servicio=cumplimiento-normativo', + newTab: false } }, { - slug: 'auditoria-normativa-ciberseguridad', + slug: 'ciberseguridad', kind: 'service', - title: 'Inspección de cumplimiento normativo y vulnerabilidades', + title: 'Ciberseguridad', short: - 'Inspección de RGPD/LSSI y vulnerabilidades básicas, con severidad y corrección recomendada por hallazgo.', + 'Revisión de vulnerabilidades básicas en tu web, con severidad y corrección recomendada por problema.', description: - 'Inspección de cumplimiento normativo y de vulnerabilidades de tu presencia web. Cada problema lleva severidad y corrección recomendada. No incluye prueba de intrusión ni certificación.', - audience: ['Negocios que necesitan un informe accionable antes de endurecer su sitio.'], + 'Inspección de seguridad básica de tu presencia web: cabeceras, secretos expuestos y superficies evidentes. Cada problema lleva severidad y corrección recomendada. No incluye prueba de intrusión ni certificación.', + audience: [ + 'Negocios que necesitan priorizar riesgos técnicos evidentes antes de endurecer el sitio.' + ], problems: [ - 'Dudas de cumplimiento RGPD/LSSI', 'Superficie de seguridad sin priorizar', - 'Necesitas un informe, no una prueba de intrusión' + 'Dudas sobre cabeceras, secretos o puntos de entrada evidentes', + 'Necesitas un informe técnico, no una prueba de intrusión' ], scope: [ - 'RGPD y LSSI en la superficie pública', 'Seguridad básica (cabeceras, secretos expuestos, superficies evidentes)', - 'Priorización por severidad' + 'Priorización por severidad', + 'Corrección recomendada por problema' ], deliverables: [ - 'Informe con problemas de normativa y seguridad básica', - 'Severidad y corrección por hallazgo' + 'Informe de seguridad básica', + 'Severidad y corrección recomendada por problema' ], process: ['Alcance del encargo', 'Análisis', 'Redacción del informe', 'Entrega'], - technicalDetails: ['Revisión proporcional OWASP ASVS L1, CSP, cookies y formularios.'], + technicalDetails: [ + 'Revisión proporcional OWASP ASVS L1, CSP y superficies públicas evidentes.' + ], exclusions: [ 'No incluye prueba de intrusión', 'No incluye certificación oficial', - 'No incluye implementación de remedios' + 'No incluye implementación de remedios', + 'No incluye revisión legal de protección de datos ni cookies' ], commercial: { mode: 'fixed', - priceEuros: provisionalPrices.auditoria, + priceEuros: provisionalPrices.ciberseguridad, priceStatus: 'provisional', payment: '50 % al encargo y 50 % a la entrega, sin recargo', timeline: '10 días laborables', - delivery: - 'Informe RGPD/LSSI y seguridad básica; cada problema con severidad y corrección recomendada', + delivery: 'Informe de seguridad básica; cada problema con severidad y corrección recomendada', guarantee: 'Corrección de desviaciones del alcance escrito en 15 días naturales' }, action: { kind: 'contact', - label: 'Pedir la inspección', - href: '/contacto?motivo=informacionCliente&servicio=auditoria-normativa-ciberseguridad', + label: 'Pedir la revisión', + href: '/contacto?motivo=informacionCliente&servicio=ciberseguridad', newTab: false } }, { slug: 'software-a-medida', kind: 'service', - title: 'Producto de software a medida', + title: 'Colaboración en proyectos de software', short: - 'Una herramienta o aplicación por fases: primero una versión pequeña y útil, luego mejoras.', + 'Me integro en proyectos grandes con más compañeros: aporto diseño y desarrollo por fases, con precio y plazo por escrito.', description: - 'Diseño y desarrollo de un producto de software para un proceso de tu negocio. Empezamos por descubrir el problema, entregamos una primera versión usable y seguimos con mejoras acordadas. Cada fase lleva su precio y plazo en la propuesta. No es una web de escaparate ni un pack cerrado.', + 'Colaboración técnica en proyectos de software con equipo existente o ampliado. Encajo en el ritmo del proyecto, entrego por fases y dejo precio y plazo escritos antes de cada una. No es una web de escaparate ni un pack cerrado; es trabajo conjunto en un producto o herramienta mayor.', audience: [ - 'Equipos pequeños o emprendedores con un proceso manual que quieren digitalizar', - 'Pymes que necesitan una herramienta propia sin construir una plataforma enorme de golpe' + 'Equipos que ya tienen un proyecto en marcha y necesitan refuerzo técnico fiable', + 'Negocios con un producto o herramienta grande donde hace falta construir por fases con más manos' ], problems: [ - 'Un proceso se hace a mano o con hojas de cálculo frágiles', - 'Las herramientas genéricas no encajan y pierdes tiempo', - 'Quieres validar una idea con una primera versión real' + 'El proyecto es grande y hace falta alguien que entregue por fases con el equipo', + 'Las herramientas genéricas no encajan y el desarrollo se atasca', + 'Necesitas ampliar capacidad sin perder claridad de alcance y plazos' ], scope: [ - 'Descubrimiento del problema y del alcance de la primera versión', - 'Diseño y construcción de la primera versión usable', + 'Alineación con el equipo y el alcance de la fase', + 'Diseño y construcción de entregables acordados', 'Mejoras por fases con precio y plazo escritos antes de cada una', - 'Entrega de código, documentación y accesos al terminar de pagar' + 'Entrega de código, documentación y accesos al terminar de pagar la fase' ], deliverables: [ 'Propuesta por fase con precio y plazo', - 'Primera versión publicada o instalable según el acuerdo', + 'Entregable de la fase publicado o integrable según el acuerdo', 'Código, documentación breve e instrucciones de uso', 'Accesos al entorno acordado' ], process: [ - 'Descubrimiento y propuesta de la primera fase', - 'Construcción de la primera versión', + 'Alineación con el equipo y propuesta de la primera fase', + 'Construcción del entregable de la fase', 'Prueba conjunta y entrega', - 'Mejoras siguientes solo si las acordamos por escrito' + 'Fases siguientes solo si las acordamos por escrito' ], technicalDetails: [ - 'Stack según el problema (web, CLI u otra superficie acordada).', + 'Stack según el problema y las convenciones del equipo (web, CLI u otra superficie acordada).', 'Caso de referencia: Zedazo (herramienta de contactos de código abierto).' ], exclusions: [ @@ -360,7 +376,7 @@ export const services: Service[] = [ }, action: { kind: 'contact', - label: 'Pedir propuesta de software', + label: 'Pedir propuesta de colaboración', href: '/contacto?motivo=propuestaComercial&origen=servicios&servicio=software-a-medida', newTab: false } diff --git a/src/lib/contactSchema.test.ts b/src/lib/contactSchema.test.ts index deee074..1786be0 100644 --- a/src/lib/contactSchema.test.ts +++ b/src/lib/contactSchema.test.ts @@ -124,7 +124,7 @@ describe('parseContactBody R04', () => { expect(parsed.data.projectSlug).toBe('front-valencia'); expect(parsed.data.origin).toBe('proceso'); const msg = buildContactMessage(parsed.data); - expect(msg).toMatch(/Informe 360/); + expect(msg).toMatch(/Posicionamiento comercial en buscadores/); expect(msg).toMatch(/Front Valencia/); expect(msg).toMatch(/Origen de página: proceso/); diff --git a/src/pages/index.astro b/src/pages/index.astro index 8481730..7b0052e 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -17,18 +17,18 @@ const paths = [ linkLabel: 'Ver webs y landings' }, { - title: 'Software a medida', - body: 'Una herramienta por fases, empezando por una versión pequeña y útil.', + title: 'Colaboración en software', + body: 'Me integro en proyectos grandes con el equipo, por fases.', price: 'Precio por fase en la propuesta', href: '/servicios/software-a-medida?origen=home', - linkLabel: 'Ver software a medida' + linkLabel: 'Ver colaboración en software' }, { title: 'Revisar tu web', - body: 'Te digo qué falla y por dónde empezar.', + body: 'Informe SEO con métricas y plan de ajustes.', price: `Desde ${formatEuro(provisionalPrices.informeComercial)} + IVA`, href: '/servicios/informe-comercial?origen=home', - linkLabel: 'Ver el Informe 360º' + linkLabel: 'Ver posicionamiento en buscadores' } ]; diff --git a/src/pages/servicios/index.astro b/src/pages/servicios/index.astro index 313be70..ece6f03 100644 --- a/src/pages/servicios/index.astro +++ b/src/pages/servicios/index.astro @@ -9,14 +9,14 @@ import { serviceProducts } from '../../content/services'; ---

diff --git a/tests/e2e/a11y.spec.ts b/tests/e2e/a11y.spec.ts index 8175bae..a214129 100644 --- a/tests/e2e/a11y.spec.ts +++ b/tests/e2e/a11y.spec.ts @@ -5,8 +5,8 @@ const routes = [ '/', '/servicios', '/servicios/informe-comercial', - '/servicios/sesion-profesional', - '/servicios/auditoria-normativa-ciberseguridad', + '/servicios/cumplimiento-normativo', + '/servicios/ciberseguridad', '/proyectos', '/proyectos/front-valencia', '/proceso-y-entrega', diff --git a/tests/e2e/cp09-a11y-ux.spec.ts b/tests/e2e/cp09-a11y-ux.spec.ts index e774321..33d7685 100644 --- a/tests/e2e/cp09-a11y-ux.spec.ts +++ b/tests/e2e/cp09-a11y-ux.spec.ts @@ -139,7 +139,7 @@ test.describe('CP-09 recorridos A–E (muestreo)', () => { test('A/B: servicios → ficha informe → Pedir el informe', async ({ page }) => { await page.goto('/servicios'); await assertSingleH1AndMain(page); - await page.getByRole('link', { name: 'Informe 360º' }).click(); + await page.getByRole('link', { name: 'Posicionamiento comercial en buscadores' }).click(); await expect(page).toHaveURL(/\/servicios\/informe-comercial/); await assertSingleH1AndMain(page); const cta = page.getByRole('link', { name: /Pedir el informe/i }); diff --git a/tests/e2e/entrega-servicios.spec.ts b/tests/e2e/entrega-servicios.spec.ts index 2285445..ba4dc39 100644 --- a/tests/e2e/entrega-servicios.spec.ts +++ b/tests/e2e/entrega-servicios.spec.ts @@ -4,20 +4,19 @@ import { formatEuro, portalTotalWithModules, provisionalPrices } from '../../src test.describe('F02 F03 F10 servicios y configurador', () => { test('F02: /servicios muestra las cuatro fichas y cada importe del módulo', async ({ page }) => { await page.goto('/servicios'); - await expect(page.getByRole('heading', { name: 'Informe 360º' })).toBeVisible(); - await expect(page.getByRole('heading', { name: 'Sesión profesional' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Hoja de Servicios' })).toBeVisible(); await expect( - page.getByRole('heading', { - name: 'Inspección de cumplimiento normativo y vulnerabilidades' - }) + page.getByRole('heading', { name: 'Posicionamiento comercial en buscadores' }) ).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Cumplimiento normativo' })).toBeVisible(); + await expect(page.getByRole('heading', { name: 'Ciberseguridad' })).toBeVisible(); await expect( - page.getByRole('heading', { name: 'Producto de software a medida' }) + page.getByRole('heading', { name: 'Colaboración en proyectos de software' }) ).toBeVisible(); const body = await page.locator('main').innerText(); expect(body).toContain(formatEuro(provisionalPrices.informeComercial)); - expect(body).toContain(formatEuro(provisionalPrices.sesionProfesional)); - expect(body).toContain(formatEuro(provisionalPrices.auditoria)); + expect(body).toContain(formatEuro(provisionalPrices.cumplimiento)); + expect(body).toContain(formatEuro(provisionalPrices.ciberseguridad)); expect(body).toContain(formatEuro(provisionalPrices.portalInicio)); expect(body).toContain(formatEuro(provisionalPrices.tienda)); expect(body).toContain(formatEuro(provisionalPrices.mensajeria)); diff --git a/tests/e2e/home-grid-nav.spec.ts b/tests/e2e/home-grid-nav.spec.ts index 9a6c9a6..14e62b8 100644 --- a/tests/e2e/home-grid-nav.spec.ts +++ b/tests/e2e/home-grid-nav.spec.ts @@ -17,7 +17,7 @@ test('F01: home es hero estable + caminos + proceso + caso', async ({ page }) => const h1 = page.getByRole('heading', { level: 1, - name: /Diseño y desarrollo de webs y software a medida/ + name: /Webs y software a medida\. Y revisión de la tuya\./ }); await expect(h1).toBeVisible(); await expect(page.getByText(/Tres caminos claros/)).toBeVisible(); @@ -45,4 +45,6 @@ test('F01: home es hero estable + caminos + proceso + caso', async ({ page }) => await expect(page.getByRole('heading', { name: 'Cómo trabajo', exact: true })).toBeVisible(); await expect(page.getByRole('heading', { name: 'Un caso', exact: true })).toBeVisible(); await expect(page.getByRole('link', { name: 'Ver el proceso completo' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Ver posicionamiento en buscadores' })).toBeVisible(); + await expect(page.getByRole('link', { name: 'Ver colaboración en software' })).toBeVisible(); }); diff --git a/tests/e2e/products-structure.spec.ts b/tests/e2e/products-structure.spec.ts index 401ad9f..9945357 100644 --- a/tests/e2e/products-structure.spec.ts +++ b/tests/e2e/products-structure.spec.ts @@ -37,8 +37,8 @@ test.describe('DET estructura común de fichas', () => { test('catálogo publicado: cuatro slugs sin portal-inicio', () => { expect(products.map((p) => p.slug)).toEqual([ 'informe-comercial', - 'sesion-profesional', - 'auditoria-normativa-ciberseguridad', + 'cumplimiento-normativo', + 'ciberseguridad', 'software-a-medida' ]); expect(products.some((p) => p.slug === 'portal-inicio')).toBe(false); diff --git a/tests/e2e/seo-edge-prod.spec.ts b/tests/e2e/seo-edge-prod.spec.ts index 99966ce..aa85384 100644 --- a/tests/e2e/seo-edge-prod.spec.ts +++ b/tests/e2e/seo-edge-prod.spec.ts @@ -11,6 +11,18 @@ import { resolve } from 'node:path'; const APEX = 'https://alexendros.dev'; const WWW = 'https://www.alexendros.dev'; +/** + * Redirects declarados en vercel.json que el apex aún puede no aplicar + * (página antigua en 200 hasta el deploy que los activa). Tras el merge, + * el mismo test exige 301/308 cuando ya estén en producción. + */ +const PENDING_APEX_REDIRECT_SOURCES = new Set([ + '/servicios/sesion-profesional', + '/servicios/sesion-profesional/', + '/servicios/auditoria-normativa-ciberseguridad', + '/servicios/auditoria-normativa-ciberseguridad/' +]); + type VercelRedirect = { source: string; destination: string; @@ -76,8 +88,19 @@ test.describe('CP-10 edge producción', () => { for (const rule of redirects) { const from = sampleSourceUrl(rule.source); const hop = await firstHop(request, from); + const pending = PENDING_APEX_REDIRECT_SOURCES.has(rule.source); + const isPermanent = [301, 308].includes(hop.status); + + if (pending && !isPermanent) { + expect( + [200, 404].includes(hop.status), + `${rule.source}: pendiente de deploy (got ${hop.status})` + ).toBe(true); + continue; + } + expect( - [301, 308].includes(hop.status), + isPermanent, `${rule.source}: primer salto debe ser permanente (got ${hop.status})` ).toBe(true); expect(hop.location, `${rule.source}: Location ausente`).toBeTruthy(); diff --git a/tests/e2e/seo-routes.spec.ts b/tests/e2e/seo-routes.spec.ts index 53eaf6d..5fc91c7 100644 --- a/tests/e2e/seo-routes.spec.ts +++ b/tests/e2e/seo-routes.spec.ts @@ -15,8 +15,8 @@ const SITEMAP_PUBLIC_PATHS = [ '/proyectos/', '/servicios/', '/servicios/informe-comercial/', - '/servicios/sesion-profesional/', - '/servicios/auditoria-normativa-ciberseguridad/', + '/servicios/cumplimiento-normativo/', + '/servicios/ciberseguridad/', '/servicios/software-a-medida/', '/sobre-mi/', '/articulos/presupuesto-web/', @@ -200,8 +200,8 @@ const TITLE_SEED_PATHS = [ '/', '/servicios', '/servicios/informe-comercial', - '/servicios/sesion-profesional', - '/servicios/auditoria-normativa-ciberseguridad', + '/servicios/cumplimiento-normativo', + '/servicios/ciberseguridad', '/proyectos', '/proceso-y-entrega', '/contacto', diff --git a/tests/e2e/ui-matrix.spec.ts b/tests/e2e/ui-matrix.spec.ts index 90f4358..8c5b882 100644 --- a/tests/e2e/ui-matrix.spec.ts +++ b/tests/e2e/ui-matrix.spec.ts @@ -10,8 +10,8 @@ const URLS = [ '/', '/servicios', '/servicios/informe-comercial', - '/servicios/sesion-profesional', - '/servicios/auditoria-normativa-ciberseguridad', + '/servicios/cumplimiento-normativo', + '/servicios/ciberseguridad', '/proyectos', '/proyectos/front-valencia', '/proceso-y-entrega', diff --git a/vercel.json b/vercel.json index 8251546..0c426b6 100644 --- a/vercel.json +++ b/vercel.json @@ -68,6 +68,26 @@ "destination": "/servicios", "permanent": true }, + { + "source": "/servicios/sesion-profesional", + "destination": "/servicios", + "permanent": true + }, + { + "source": "/servicios/sesion-profesional/", + "destination": "/servicios", + "permanent": true + }, + { + "source": "/servicios/auditoria-normativa-ciberseguridad", + "destination": "/servicios/cumplimiento-normativo", + "permanent": true + }, + { + "source": "/servicios/auditoria-normativa-ciberseguridad/", + "destination": "/servicios/cumplimiento-normativo", + "permanent": true + }, { "source": "/servicios/web-para", "destination": "/servicios",