Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -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.
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
23 changes: 19 additions & 4 deletions docs/audits/decisions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."
}
]
}
Binary file modified public/og/servicios.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file added public/og/servicios/ciberseguridad.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added public/og/servicios/cumplimiento-normativo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified public/og/servicios/informe-comercial.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed public/og/servicios/sesion-profesional.png
Binary file not shown.
Binary file modified public/og/servicios/software-a-medida.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion scripts/gen-og.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
2 changes: 1 addition & 1 deletion src/components/PricingConfigurator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function PricingConfigurator() {
return (
<div data-pricing-configurator className="rounded-lg border border-border bg-card p-6 md:p-8">
<h2 id="portal-heading" className="text-xl font-bold">
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.
</h2>
<p className="mt-2 text-sm text-muted">
Base del portal: {portalBase.delivery}. Pago: {portalBase.payment}. Plazo:{' '}
Expand Down
4 changes: 2 additions & 2 deletions src/components/StickyCta.astro
Original file line number Diff line number Diff line change
Expand Up @@ -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"
>
<div class="mx-auto flex max-w-6xl items-center justify-between gap-3 px-4 py-2.5">
<p class="min-w-0 text-sm">
<strong class="font-bold">Informe 360º {priceLabel}</strong>
<strong class="font-bold">Posicionamiento SEO {priceLabel}</strong>
<span class="text-muted"> · orientativo</span>
</p>
<div class="flex shrink-0 items-center gap-1">
Expand Down
2 changes: 1 addition & 1 deletion src/content/articles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.'
]
}
];
Expand Down
19 changes: 10 additions & 9 deletions src/content/commercial-coherence.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
26 changes: 12 additions & 14 deletions src/content/contact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
Loading
Loading