Saltar al contenido principal

Prompts para Contract Testing y Validación de Schemas con IA

8 prompts para evitar que tu API rompa a otros equipos: contract testing, validación con OpenAPI, JSON Schema, Pact y tests de compatibilidad.

Fran Cobos 21 min de lectura 4019 palabras

Tabla de contenidos

La historia es siempre la misma. Tu API funcionaba. Otro equipo la consume. Entras en su PR, cambias el campo total de número a string porque “así queda mejor”, y su build se rompe.

No es culpa suya. Tú no rompiste nada: nunca te dijo que ese campo era intocable. Ese es el problema, y tiene nombre: implicit contract.

Un contrato implícito es lo que tu equipo asume que hace la API sin haberlo escrito en ningún sitio donde una máquina pueda verificarlo. Es el más común y el más caro. Se rompe un viernes, desde otro repositorio, y el mensaje que aparece es un TypeError: expected number, got string en una pantalla que nadie de tu equipo ha visto jamás.

Este artículo te da los 8 prompts para convertir tu API en un contrato explícito, validado en CI y protegido contra breaking changes, y para que la IA te ayude a hacerlo con el código que ya tienes.

Si ya tienes la API funcionando y lo que quieres es documentarla, empieza por aquí. Si lo que tienes es una app con llamadas a la IA que fallan, eso es otro tema: la guía de function calling cubre la parte de conectar modelos con APIs.

Cinco reglas antes de definir el contrato

1. El contrato se escribe una vez y se genera el resto

Si tienes un .yaml de OpenAPI y además tipos de TypeScript escritos a mano, tienes dos fuentes de verdad que divergen. Elige una fuente primary: casi siempre el código, con generación del resto (tipos, clientes, validación, documentación). Escribirlo todo a mano triple la posibilidad de que uno se quede viejo.

2. Añadir es seguro, quitar no

Regla universal de diseño de APIs: cambios aditivos siempre son compatibles. Añadir un campo opcional, un endpoint, un valor de enum, un header de respuesta. Romper es quitar un campo, cambiar su tipo, hacerlo obligatorio, alterar un valor de enum existente, cambiar un status code o mover un endpoint.

3. Valida en la frontera, no en el interior

Una API que no valida su entrada está violando su propio contrato. La validación con Zod o ajv tiene que estar en el borde: ahí es donde puedes devolver un 400 claro en lugar de un 500 con un stack trace en producción.

4. Los errores también son contrato

El 400 con {"error": {"code": "INVALID_TOTAL", "message": "..."}} es un contrato tan firme como el 200. El 500 Internal Server Error vacío es una decisión que tu consumidor va a acabar pagando. Códigos de error estables desde el día uno.

5. El consumidor manda en el ritmo, el proveedor en el detalle

No puedes obligar a que te actualicen a una v2 mañana. Por eso el versionado y los campos deprecados con fecha existen. Un contrato vivo tiene deprecated: true y x-sunset-date, no una reunión de aviso.

1. Extraer el contrato implícito desde el código existente

Este es el punto de partida cuando tienes una API que ya funciona y nunca documentó.

Prompt:

Tengo esta API en producción y quiero extraer su contrato real:

ARCHIVOS: [PEGAR RUTAS: controllers, schemas, tipos]
STACK: [Node.js + Express / Fastify / NestJS / Next.js route handlers]
BASE DE DATOS: [POSTGRES / MONGO / SQL]

Genera un contrato OpenAPI 3.1 en YAML que refleje lo que el código HACE
REALMENTE ahora, no lo que dice la documentación (si existe, ignórala).

Para cada endpoint extrae:
- método, path, query params, path params, headers
- request body: schema JSON real, con required, tipos, formatos, defaults,
  límites (min/max/minLength) que seapliquen de verdad en el código
- response 200: schema real, no el ideal
- CÓDIGO de error: TODOS los status que puede devolver ese endpoint, con
  su body real. Busca los throw, los next(err), los status() del código y
  no te quedes en el happy path.
- autenticación requerida por endpoint (lee los middlewares)
- headers de respuesta que importan (RateLimit, Location, ETag)
- rate limiting si lo hay

Añade un campo description por cada propiedad que usarías para documentarlo,
y marca en example un body real quehas construido.

Al final, lista los endpoints donde el código y la documentación actual
discrepan, si es que hay documentación.

Devuelve el YAML completo. No inventes endpoints que no estés leyendo.

Por qué funciona: pedir explícitamente el happy path más los errores suele revelar que la mitad de los endpoints nunca documentó sus estados de fallo. Y extractlo del código garantiza que el spec inicial describe la realidad, no la intención.

2. Validar request y response con Zod: del schema al runtime

Un schema que solo sirve para generar tipos no valida nada. La gracia de Zod es tener una sola definición que hace las dos cosas.

Prompt:

Tengo este schema Zod:

[PEGAR SCHEMA ZOD]

Y este endpoint que lo consume:

[PEGAR HANDLER]

Genera la capa de validación completa:
1. parse del body, query y params, con errores en formato de mi API:
   { "error": { "code": "VALIDATION_ERROR", "message": "...",
     "details": [{ "path": "items[0].total", "message": "..." }] } }
2. Un middleware reusable que valida y devuelve 400 con ese formato.
3. Validación del RESPONSE también: en desarrollo, un wrapper que valida
   lo que sale y loguea un error si no cumple el schema. Esto detecta
   las regresiones de contrato en tu propio CI antes de desplegar.
4. Tipos inferidos: z.infer, con el input y el output diferenciados
   (los .default() cambian el tipo de salida respecto al de entrada).
5. Un test con vitest que cubra: body válido, cada campo requerido ausente,
   cada tipo incorrecto, y los casos borde (array vacío, string de longitud
   máxima, null vs undefined).

Escribe TypeScript con tipos estrictos, sin `any`.

Por qué funciona: el paso 3 es el que casi nadie hace y el que más valor da. Validar la respuesta en desarrollo convierte un fallo de contrato en desarrollo en un error legible en tu CI.

3. Validar contra OpenAPI con ajv en tiempo de ejecución

Si tienes un contrato publicado y terceros lo consumen, la validación tiene que ser contra el contrato, no contra tus tipos internos.

Prompt:

Tengo un contrato OpenAPI 3.1 en openapi.yaml y quiero validar el tráfico
real contra él en tiempo de ejecución:

STACK: [Node.js + Express, o Fastify]
ENTORNO: [producción con alto tráfico]

Genera:
1. Configuración de ajv con el draft 2020-12, strict: false (los schemas de
   OpenAPI tienen keywords que ajv no conoce) y los formatos de OpenAPI
   sobreescritos para JSON Schema (int32, int64, date-time, uuid, uri).
2. Un middleware que:
   - valide request (body, params, query, headers) y devuelva 400 con el
     path exacto del campo que falla
   - valide response en modo "shadow": si falla, solo registra el error con
     el path del endpoint y no rompe la petición. Esto es crítico: si
     bloqueas responses inválidos en producción, un fallo de contrato se
     convierte en una caída.
   - en staging, sí devuelve 500 si el response no cumple.
3. Normalización previa: ajv no entiende nullable en OpenAPI 3.0 ni
   nullable vs optional en 3.1. Crea un pre-proceso que convierta el schema.
4. Un script que descargue el spec de un entorno anterior (por ejemplo el
   deployed hace una hora) y lo compare con el actual para detectar cambios
   antes de desplegar.
5. Métricas: cuéntame cuántos requests fallan validación por endpoint y por
   campo, y expónlas en formato Prometheus.

Devuelve los ficheros TypeScript completos.

Por qué funciona: el modo shadow es el detalle que hace viable esto en producción. Aprendes de los fallos sin crear ninguno. Y el 3 es un problema real: el mismo semántica (nullable) se representa distinto en OpenAPI 3.0 y 3.1.

4. Detectar breaking changes en el CI

Prompt:

Quiero un job de CI que detecte breaking changes en mi API antes de
mergear:

ARCHIVO DE SPEC: [openapi.yaml]
SPEC PUBLICADO: [PEGAR URL, ej. https://api.ejemplo.com/openapi.json]
CI: [GITHUB ACTIONS]

Genera:
1. oasdiff breaking openapi.yaml <spec-publicado> con la configuración que
   ignore las adiciones opcionales pero marque como breaking:
   - eliminación de campo, endpoint o parámetro
   - cambio de tipo (string → number, object → string)
   - campo opcional que pasa a obligatorio
   - nuevo valor en un enum con restriction (si el enum es cerrado)
   - cambio de un status code de éxito
   - reducción de min/max, minItems/maxItems, minLength
   - eliminación de un security scheme o reducción de permisos
2. El fichero workflow que descarga el spec publicado, ejecuta oasdiff y
   falla el job si hay breaking changes.
3. Un script "spec:accept" para cuando el cambio es intencionado: actualiza
   el snapshot en versionado, genera un CHANGELOG.md con los cambios
   clasificados (BREAKING / added / deprecated / fixed) y pide confirmación.
4. Un endpoint /version o el campo api-version en el health check que
   devuelva el hash del spec desplegado, para poder comparar contra lo que
   realmente está en producción y no contra un spec stale.

Incluye cómo generar el CHANGELOG con breaking changes en negrita, que es
lo que leen los otros equipos.

Por qué funciona: comparar contra el spec publicado y no contra git es el detalle clave. Si comparas contra el spec del main, otro equipo ya ha consumido la versión nueva y tu check pasa tarde.

Si quieres velocidad a la hora de detectar el fallo, la calculadora de precios de IA no aplica aquí, pero sí aplica si tu API es un proxy de modelos: ahí cada llamada es un coste y los errores 429 son un riesgo constante que sí conviene diseñar con margen.

5. Montar Pact para contratos consumer-driven

Con OpenAPI verificas que cumples lo que declaras. Con Pact verificas que sigues cubriendo lo que tus consumidores usan.

Prompt:

Quiero implementar contract testing consumer-driven con Pact:

PROVEEDOR: API de [NOMBRE] en [REPO], stack [Express/Fastify]
CONSUMIDOR 1: [SEGUNDO EQUIPO/REPO], usa [GET /pedidos, POST /pedidos]
CONSUMIDOR 2: [OTRO CONSUMIDOR], usa [GET /pedidos/:id]

Genera:
1. En el repo del CONSUMIDOR: los tests de contrato con @pact/pact-jest o
   @pact/pact-js (los tests se ejecutan contra un mock del proveedor, sin
   necesitar el proveedor levantado).
   - Cada interaction con: given, when, then (state, request, response).
   - Usa providerStates para los datos que el mock necesita.
   - Genera el JSON del contrato en contracts/ y súbelo al broker.
2. En el repo del PROVEEDOR: el test de verificación que descarga los
   pactos del broker y levanta la API real para comprobar que cumple.
   - providerStates resueltos con datos de seed reales.
   - Mocks deshabilitados (allowMultiple: false) para que si el proveedor
     llama a una dependencia que no toca el contrato, salte.
3. docker-compose con el Pact Broker para desarrollo local.
4. El workflow de GitHub Actions para ambos repos, con el broker en CI
   (usando el docker image oficial, sin SaaS).
5. Explica qué NO cubre Pact y cómo se complementa con los tests de
   integración normales.

Un detalle: el JSON del contrato es un artefacto versionado. Debe estar en
el repo del consumidor, commiteado, y los cambios en él deben generar un
CHANGELOG para el proveedor.

Devuelve los ficheros de test completos y ejecutables.

Por qué funciona: los providerStates son la parte que más se olvida y la que hace que el test del proveedor falle por datos en lugar de por el contrato. Sin provider states bien resueltos, el falso positivo es constante.

6. Detectar incompatibilidades desde el lado del consumidor

El otro equipo no tiene por qué esperar a que publiques una breaking change.

Prompt:

Soy el consumidor de esta API. Este es el cliente con el que llamo:

[PEGAR CLIENTE: axios/fetch generado, o el código de las llamadas]

Y esta es una versión nueva del OpenAPI que el proveedor quiere desplegar:

[PEGAR openapi.yaml NUEVO]
La que uso hoy es: [PEGAR openapi.yaml VIEJO o URL]

Analiza el impacto en MI código:
1. ¿Qué llamadas mías romperían con este spec? Lista endpoint + método +
   línea de mi cliente.
2. ¿Qué campos que yo leo ya no existen o han cambiado de tipo?
3. ¿Qué campos nuevos obligatorios tendría que enviar?
4. ¿Qué valores de enum uso que ya no son válidos?
5. ¿Cuánto trabajo de migración implica? Estima: solo ajustes de tipos,
   o hay que cambiar lógica.

Y dame el código de migración: el cliente actualizado con los tipos
correctos, un flag de versión si coexisten ambas versiones, y los tests que
verifican que mi código sigue funcionando contra la versión antigua
mientras migro.

Devuelve un informe de impacto + el código de migración.

Por qué funciona: poner al consumidor a hacer el análisis antes del merge del proveedor convierte una sorpresa en un trabajo planificado. Y el cliente con flag de versión es lo que permite migrar sin ventana de mantenimiento.

7. Diseñar el versionado y una estrategia de deprecación

Prompt:

Necesito una estrategia de versionado y deprecación para mi API:

USUARIOS INTERNOS: [n equipos, se despliegan a diario]
USUARIOS EXTERNOS: [n clientes, no controlo su despliegue]
CAMBIO PENDIENTE: [DESCRIBIR: eliminar campo X, cambiar tipo de Y]

Quiero un enfoque REST, no GraphQL.

Genera:
1. Comparación de estrategias: versionado en path (/v1/, /v2/) vs header
   (Accept: application/vnd.api+2) vs fechas en el path. Con pros y contras
   reales para mi caso, no genéricos. Empieza por recomendar una.
2. Plan de deprecación completo:
   - Cómo se marca la deprecación en el spec (deprecated: true en OpenAPI,
     Sunset header RFC 8594).
   - El flujo real: aviso → periodo de convivencia → apagado.
   - Qué headers se devuelven durante la deprecación y qué dice cada uno
     (Deprecation, Sunset, Link rel="deprecation").
   - Cómo medir el uso de cada versión para saber cuándo se puede apagar
     v1 (métrica concreta en el código).
3. Código del middleware que inyecta los headers de deprecación, y del
   endpoint /status que reporta el uso por versión.
4. Qué pasa con los webhooks que TÚ emites a otros equipos: ¿también se
   versionan? ¿Cómo se comunica el cambio de payload?

Un punto clave: quiero poder apagar la v1 en base a datos, no en base a una
fecha. Dame la métrica exacta.

Por qué funciona: el punto 5 es donde se enfocan casi todos los proyectos y donde más caro sale. Si tu API emite eventos o webhooks, ese canal es otro contrato, con otros consumidores, y suele estar mucho menos cuidado que el REST. La guía de webhooks cubre a fondo el caso de Stripe, que es donde más se nota.

8. Generar tests de contrato a partir del spec

Prompt:

Tengo este OpenAPI 3.1: [PEGAR openapi.yaml]
Stack de test: [VITEST + SUPERTEST / PLAYWRIGHT]
Objetivo: generar tests de contrato que cubran cada endpoint y cada
status code declarado.

Genera:
1. Un test por endpoint y por status code declarado en el spec:
   - response válido completo
   - request con todos los campos requeridos
   - cada status de error: verifica status Y el schema del body de error
   - campos requeridos ausentes en el request (por cada uno)
   - tipos incorrectos
   - unknown properties: ¿el endpoint las rechaza (additionalProperties: false)
     o las ignora? Testea el comportamiento REAL.
2. Un test de property-based testing con fast-check: genera inputs aleatorios
   válidos según el schema y verifica que nunca hay un 500. Un 500 con
   input que cumple el schema significa que hay un fallo de validación en
   el código.
3. Verificación de que el response cumple el schema DECLARADO, no solo
   código válido.
4. Un informe que compare: endpoints declarados en el spec vs endpoints
   realmente implementados vs endpoints testeados. Los tres conjuntos
   deberían ser iguales; si no, esa diferencia es lo que hay que arreglar.

Escribe TypeScript. Genera un helper para validar contra el schema que
puedas reutilizar.

Por qué funciona: el property-based testing (punto 2) es lo que encuentra los huecos reales. Los tests escritos a mano prueban lo que el autor pensó; los generados a partir del schema prueban todo el espacio del schema.

El catálogo de herramientas y cuándo usar cada una

HerramientaQué validaMomentoCI bloqueaLimitación principal
ZodTu shape de datos en runtimeEn cada requestSí (con types)No valida contra el spec publicado
ajv + OpenAPIContrato publicado, request y responseEn cada request (shadow)En stagingOverhead; hay que normalizar nullable
oasdiffBreaking changes entre versionesEn PRSíSolo compara specs, no runtime
PactLo que tus consumidores usan de verdadEn PR del proveedorSíRequiere broker y provider states
SchemathesisProperty-based sobre OpenAPIEn nightlyOpcionalRequiere la API levantada
fast-checkGenerador de inputs desde schemaEn cada PRSíHay que decidir qué oráculo usar
SonarQubeDeuda técnica y complejidadContinuoNoNo entiende semántica de API
OpenAPI diff UIComparación visual de specsEn revisiónNoManual, no automatizable

La combinación que mejor funciona en la práctica es Zod en runtime + oasdiff en CI + Pact entre equipos con SLA acordado. Los tres cubren capas distintas: la forma de los datos, la compatibilidad con lo publicado y la compatibilidad con lo que te piden.

Anti-patrones en contratos

❌ El spec como documento decorativo

# ❌ openapi.yaml que nadie ejecuta, escrito a mano en 2023
components:
  schemas:
    Pedido:
      properties:
        total:
          type: number   # el código ya dice string desde hace 6 meses
// ✅ El spec se valida contra la realidad en cada request y en cada PR
const responseSchema = compile(schemaPedido);
if (!responseSchema(cuerpo)) {
  logger.error({ path, errors: responseSchema.errors }, 'contrato roto');
}

❌ Introducir un campo opcional para todo

// ❌ "Por si acaso lo necesito luego"
interface Pedido {
  id: string;
  cliente?: Cliente;
 /items?: Item[];
  metadata?: Record<string, unknown>;  // cajón de sastre
  legacy_id?: string;                  // nadie sabe qué es
}
// ✅ Campos explícitos, deprecaciones declaradas con fecha
interface Pedido {
  id: string;
  cliente: Cliente;
  items: Item[];
  /** @deprecated desde 2026-03-01, se elimina 2027-01-01. Usar externalRef. */
  legacy_id?: string;
}

❌ Validar solo el request

// ❌ Solo comprueba que lo que entra es válido
app.post('/pedidos', validateBody(pedidoSchema), crearPedido);

// ✅ Valida también lo que sale: detecta regresiones de contrato
app.post('/pedidos', validateBody(pedidoSchema), crearPedido);
app.use(validateResponse(pedidoSchema, { mode: process.env.NODE_ENV === 'production' ? 'shadow' : 'strict' }));

❌ Versionar por miedo a cada cambio

// ❌ Tres versiones para tres campos opcionales
router.use('/v1', v1); router.use('/v2', v2); router.use('/v3', v3);
// ✅ Una versión, campos aditivos, deprecación con fecha
router.use('/v1', v1);
// v2 solo cuando el cambio sea realmente incompatible,
// y con periodo de convivencia medido por uso real

❌ Breaking change en un PR “pequeño”

# ❌ "Solo quito este campo, no merece un changelog"
-  total: number
+  total: string

Con oasdiff en el CI, esto ni llega a merge. Y no debería llegar. El coste de un de-merge es una tarde; el coste de romper el build de otro equipo es un incidente.

Errores frecuentes y cómo se resuelven

El spec se desincroniza del código en dos semanas Causa casi siempre la misma: dos fuentes de verdad. Elige el código como fuente primary y genera el spec (con tsoa, zod-to-openapi, NestJS Swagger o los decorators de NestJS). Si necesitas editar el spec a mano, escribe un test que compare el spec generado con el commiteado y falle en CI si difieren.

Pact falla en el proveedor con “unexpected request” El proveedor está llamando a una dependencia que el contrato no cubre, o el providerState no está configurado. Los mocks de Pact son estrictos por diseño: por eso son útiles. Resuélvelo preparando el estado en el providerStates del test del proveedor, no desactivando el mock.

El breaking change lo detecta tu equipo tarde El problema es la dirección de la verificación. Si solo verificas tu spec contra el anterior, pero otro ya consumió la versión anterior, no hay nada que hacer. Lo que funciona: publicar el spec y que los consumidores lo verificen en su CI, o usar Pact, donde el consumidor declara qué necesita y tu CI lo comprueba.

Los errores 400 son un {"message": "Bad Request"} genérico No es un detalle de UX, es un contrato roto. El consumidor no puede distinguir entre “campo faltante” y “valor inválido” si el mensaje es el mismo, así que acaba haciendo try/catch sobre el texto. Define un formato único de error desde el primer día y aplícalo con un helper, no endpoint por endpoint.

El rate limiting rompe los tests de integración Los tests de contrato hacen muchas peticiones seguidas y los limitadores los cortan. En el entorno de test, desactiva el limitador por IP o sube el umbral hasta 10x; pero prueba el limitador en un test aparte, con su propio presupuesto, para que no desaparezca de la cobertura.

Schemathesis genera un 500 con un input perfectamente válido Ha encontrado un bug real. Casi siempre es una excepción no capturada en el camino feliz: una dependencia que devuelve null, una división por cero, un await que nadie esperó. Este es el mejor hallazgo que puede darte la IA: un caso de entrada que cumple tu propio contrato y que tu contrato no captura.

El flujo completo

┌───────────────────────────────────────────────────────────┐
│  CÓDIGO EXISTENTE (Fastify + Zod)                         │
└───────────────────────────┬───────────────────────────────┘
                            ▼  prompt 1
┌───────────────────────────────────────────────────────────┐
│  openapi.yaml extraído del código real (incl. errores)    │
└───────────┬───────────────────────────────────────────────┘
            ▼  prompt 2
┌───────────────────────────────────────────────────────────┐
│  Zod: validación de request y shadow de response          │
└───────────┬───────────────────────────────────────────────┘
            ▼  prompt 3
┌───────────────────────────────────────────────────────────┐
│  ajv + OpenAPI en runtime (shadow en prod, strict en CI)  │
└───────────┬───────────────────────────────────────────────┘
            ▼  prompt 8
┌───────────────────────────────────────────────────────────┐
│  Tests de contrato: cada status, cada campo, property-based│
└───────────┬───────────────────────────────────────────────┘
            ▼  prompt 4
┌───────────────────────────────────────────────────────────┐
│  CI: oasdiff contra el spec PUBLICADO → ¿breaking?        │
└─────┬───────────────────────────────┬─────────────────────┘
      │ NO breaking                    │ BREAKING
      ▼                                ▼
┌───────────────┐         ┌─────────────────────────────┐
│  Merge        │         │ spec:accept → CHANGELOG      │
└───────────────┘         │ + deprecación con Sunset    │
                          └──────────────┬──────────────┘
                                         ▼
                              ┌──────────────────────┐
                              │  prompt 5: Pact      │
                              │  consumidores        │
                              │  declaran qué usan   │
                              └──────────┬───────────┘
                                         ▼
                              ┌──────────────────────┐
                              │  prompt 6: análisis │
                              │  de impacto          │
                              └──────────┬───────────┘
                                         ▼
                              ┌──────────────────────┐
                              │  Apagado de v1 por  │
                              │  MÉTRICA, no fecha   │
                              └──────────────────────┘

El bucle se cierra en dos sitios: el spec publicado (que se compara en cada PR) y el broker de Pact (que te dice qué piden tus consumidores). Ninguno de los dos requiere que nadie se acuerde.

Conclusión

Una API sin contrato explícito no es una API sin documentación: es una API con documentación implícita, que es peor, porque es invisible y no se puede verificar.

Estos 8 prompts cubren el ciclo entero: extraer el contrato desde el código que ya tienes, validarlo en runtime con Zod y ajv, generar los tests que cubren cada status declarado, detectar breaking changes en el CI antes de que se fusionen, y cerrar el círculo con Pact para saber qué necesitan tus consumidores de verdad.

Lo que cambia el juego no es la herramienta, es la dirección de la verificación. Mientras solo compruebes tu spec contra el anterior, alguien va a romperse por ti. Cuando cada consumidor verifica su propio uso contra el spec publicado, el problema se convierte en un mensaje de CI de quien lo va a sufrir.

Si tienes la API funcionando pero te da pereza escribir el spec porque no sabes cómo empezar, el prompt 1 te lo hace en una pasada. Y si el problema que tienes no es de contrato sino de que tu app se rompe cuando el modelo responde raro, vuelve a parsear JSON con IA: la validación en la frontera empieza por ahí.

Y si quieres estructurar alguno de estos prompts antes de pegarlos, el generador de prompts te ahorra el trabajo de montar el bloque con el contexto, el rol y el formato de salida.

Un contrato bien definido no te impide ser flexible. Te da permiso para cambiar rápido, porque sabes qué te va a romper y a quién.

Fran Cobos

Fran Cobos

Desarrollador Full Stack especializado en IA aplicada, automatización y desarrollo web. Escribo sobre herramientas, tutoriales y casos reales para programadores.

¿Necesitas desarrollo a medida?

Apps web, IA, módulos ERP — cuéntame tu proyecto.